Gibt es eine Alternative zu den A-B-C-Schaltern von grep (um einige Zeilen vorher und nachher zu drucken)?

10
grep -A 2 -B 3 

druckt 2 Zeilen nach der Grep-Zeichenfolge und 3 Zeilen vor.

grep -C 3

druckt 3 Zeilen vor und 3 Zeilen nach

Leider unterstützt das von grepmir verwendete diese Optionen nicht. Gibt es alternative Befehle oder Skripte, um dies zu simulieren? Verwenden Sie sed/ awk/ perl/ Shell-Skripte?

Prashant Bhate
quelle
+1 Ich wusste nicht, dass es einen -CSchalter gibt.
Lazer
1
Installieren Sie GNUs grep. Im Allgemeinen war der erste Schritt bei der Einrichtung, als eine neue Sun-Maschine hier ankam, das, was jemand anrief GNU > /usr/local. Die GNU-Programme haben viele sehr nützliche Erweiterungen und sollen willkürliche Einschränkungen vermeiden (aber Sie zahlen teuer in Größe und manchmal Leistung). Viele Propietärsysteme verfügen über "inoffizielle" Paket-Repositorys mit GNU und anderen Tools. Der "Partner" wird Ihnen nichts darüber erzählen, selbst wenn sie vom Verkäufer verwaltet werden ...
vonbrand

Antworten:

6

Ein mäßig hässlicher Weg, dies zu tun, ist

grep -v pattern file >file.tmp; diff -c file.tmp file

oder ersetzen -cmit -C NUMfür NUMZeilen Kontext. Es wird jedoch zusätzliche Ausgabe erzeugen. (Wenn Sie / diffunterstützen , wird es sauberer.)-u-U NUM

Wenn Sie diffnicht -c/ -C/ haben -u, gibt es immer noch Möglichkeiten, dies zu tun, aber sie sind ziemlich hässlich. Auf der anderen Seite hat ein System, diffdas nicht einmal unterstützt, -cwahrscheinlich auch kein Perl.

Geekosaurier
quelle
Das ist cool. Funktioniert wie ein Zauber, obwohl ich die Option -bitw verwenden musste, damit es für von Windows generierte Dateien funktioniert.
Prashant Bhate
Sie können stdin an diff senden und das temporäre überspringen:grep -v pattern file | diff -c - file
Cascabel
5

ack erfordert nur Perl, und enthält -A, -Bund -COptionen , die Arbeit wie grep. Es verwendet die Regex-Syntax von Perl anstelle der von grep, und die Art und Weise, wie Dateien für die Suche ausgewählt werden, ist ganz anders. Möglicherweise möchten Sie die -fOption ausprobieren, wenn Sie sie verwenden (wodurch die zu durchsuchenden Dateien ausgedruckt werden, ohne dass tatsächlich etwas gesucht wird).

Es kann als einzelnes Skript installiert werden, für das keine Nicht-Core-Module erforderlich sind. Legen Sie es einfach in Ihrem ~/binVerzeichnis (oder an einer anderen Stelle in Ihrem Pfad, auf die Sie Schreibzugriff haben) ab und stellen Sie sicher, dass es chmodausführbar ist.

cjm
quelle
Seine Produktionsbox und leider habe ich nicht genug Privileg, etwas zu installieren, und ich kann es nicht riskieren, aber danke für diesen Tipp, ich werde es installieren und auf meinem Heim-Laptop versuchen
Prashant Bhate
@Prashant, Sie benötigen kein root, um es ackfür Ihren eigenen Gebrauch zu installieren .
CJM
Ja, aber ich kann es dort immer noch nicht verwenden, obwohl es sicher ist, dass dieses Skript für immer in meinem ~ / bin bleiben wird :)
Prashant Bhate
@Prashant: Warum kannst du es nicht benutzen? Es ist nur ein Perl-Skript.
Intuited
1
Die PRODUCTION-Box muss spezielle Genehmigungen einholen. Bla bla bla ... um irgendetwas daran zu tun. und irgendetwas geht dort schief, kommt auf meinen Kopf;) und es ist es nicht wert :)
Prashant Bhate
5

Dieses einfache Perl-Skript emuliert grep -Abis zu einem gewissen Grad

#!/usr/bin/perl

$pattern=shift; #patthern to search
$lines=shift; # number of lines to print

$n = 0;
while (<>) {
  $n = $lines if /$pattern/; # reset counting
  if ($n) { print; $n-- } # print if within
  $n = 0 if eof; # don't leak across file boundaries
}

Beachten Sie, dass Sie eine Verwendungsanweisung hinzufügen können, um das Skript lesbar und verwendbar zu machen;)

USAGE:    $./grep-A.pl <pattern> <numLines> <filename> 
Vijay Anant
quelle
Schön, welche Perl-Version brauche ich, um das auszuführen?
Prashant Bhate
Ich benutze v5.10.1, ich denke Perl 5 ist heutzutage ziemlich verbreitet.
Vijay Anant
ya es ist 5.8.8 und es funktioniert, großartig, aber ich brauche ein Skript, das tut, was -B tut
Prashant Bhate
Gut. Ich würde jedoch die Reihenfolge der Argumente ändern; grep-A 3 foosieht viel natürlicher aus als grep-A foo 3. :-)
Musiphil
3

Sie können einfach GNU grep oder Ack installieren (in Perl geschrieben, versteht viele der Optionen von GNU grep und mehr).

Wenn Sie sich lieber an Standardtools und ein wenig Skripterstellung halten möchten, finden Sie hier ein awk- Skript, das das Verhalten von GNU-Greps -Aund -Optionen emuliert -B. Minimal getestet.

#!/bin/sh
# grep-ac: a grep-like awk script
# Arguments: pattern = awk regexp to search for
#            before = number of lines to print before a match
#            after = number of lines to print after a match
{ "exec" "awk" "-f" "$0" "$@"; }
# The array h contains the history of lines that haven't been printed
# but are eligible for being "before" lines.
# The variable until contains the number of the last "after" line to print.
match($0, pattern) {   # the current line matches
    for (i in h) {
        print h[i];    # print each remaining before line
        delete h[i];   # delete each line as it's printed
    }
    until=NR+after;    # record the last after line to print
}
{
    if (NR<=until) print $0;    # from a match to its last after line: print
    else h[NR]=$0;              # after that: save in history
    delete h[NR-before];        # remove line too old to be a before line
}
END {exit !until}               # exit status: 0 if there was a match, else 1

Führen Sie es , wie , grep-ac -vpattern=PATTERN -vbefore=NBEFORE -vafter=NAFTERwo PATTERNist das Muster für (ein suchen erweitern regulären Ausdruck mit wenigen awk Ergänzungen ), und NBEFOREund NAFTERsind die Zahlen der Zeilen vor und nach einem Spiel zu drucken bzw. (säumigen auf 0). Beispiel:

<input_file grep-ac -vbefore=2 -vpattern='foo *bar'
Gilles 'SO - hör auf böse zu sein'
quelle
Jede Lösung, die Daten in einem Array speichert, kommt nicht in Frage ... Wie ich bereits erwähnt habe, ist die Dateigröße ziemlich groß und kann überlaufen. Auch awk auf diesem System erlaubt keine Dateigröße von mehr als 3000 Bytes.
Prashant Bhate
2
@Prashant: Ich verstehe deine Einwände nicht. Dieses Skript löscht Zeilen, sobald sie nicht mehr als Vorzeilen zulässig sind. Es wird nicht mehr Speicher benötigt, als aufgrund der Anforderungen von Natur aus erforderlich ist, außer dass awk möglicherweise einen höheren Overhead hat als ein Spezialprogramm (aber weniger als Perl, das Sie ebenfalls in Betracht ziehen). Die Gesamtgröße der Datei ist völlig irrelevant.
Gilles 'SO - hör auf böse zu sein'
2
{ "exec" "awk" "-f" "$0" "$@"; }: Sehr geschickter Weg, um die Einschränkungen beim Shebang-Line-Parsing zu umgehen.
Dubiousjim
2

Es stellt sich heraus, dass es ziemlich schwierig ist, -B zu emulieren, da Probleme auftreten, wenn übereinstimmende Linien direkt aufeinander folgen. Dies verbietet die Verwendung von Single-Pass-Through-Datei-Scans.

Ich habe dies beim Herumspielen mit folgender Annäherung erkannt:

perl -pe 'if(/search_term/) {print foreach @A; print ">"; $B=4}; shift @A if push(@A, $_)>7; $_ = "" unless ($B-- > 0);' target_file

Dies funktioniert ungefähr korrekt wie grep -A7 -B3, mit der im ersten Absatz beschriebenen Einschränkung.

Eine alternative (auch Einzeldatei-) Lösung für dieses Problem besteht darin, perl eine Befehlszeichenfolge zu verwenden:

sed -n `perl -pe '$_=(/search_term/?sprintf("%d,%dp;", $.-3,$.+4):"")' file` file
user455
quelle
Ziemlich langer Oneliner, aber diese Datei ist sehr groß, daher ist es in diesem Fall eine schlechte Idee, Zeilen in ein Array zu verschieben, nicht wahr?
Prashant Bhate
Das shift @A if push(@A,$_)>7;Bit behält nur ein Array mit maximaler Größe 7 bei. (das ist dein -A-Parameter). Die zweite Option enthält eine unglaublich kleine Datei (führen Sie die Perl-Datei einfach ohne die äußere Ebene aus, um zu sehen, was dort generiert wird), liest die Datei jedoch zweimal.
user455
0

Mit können sedSie zuerst die Zeilennummern übereinstimmender Zeilen abrufen, eine bestimmte Zeilennummer in einer whileSchleife dekrementieren und inkrementieren und dann sed -n "n1,n2p"Zeilen mit führendem ( n1) und nachfolgendem ( n2) Kontext drucken (ähnlich der sedvon user455 vorgeschlagenen Alternative). Viele Lesevorgänge können jedoch zu Leistungseinbußen führen.

edkann direkt auf die vorherigen und folgenden Zeilen einer übereinstimmenden Zeile verweisen, schlägt jedoch fehl, wenn der angegebene Zeilenbereich nicht vorhanden ist; Die übereinstimmende Zeile ist beispielsweise die Zeile 2, es sollten jedoch 5 Zeilen vor der Übereinstimmung gedruckt werden. Die Verwendung edist daher erforderlich, um am Anfang und am Ende eine angemessene Anzahl von (leeren) Zeilen hinzuzufügen. (Für große Dateien ist dies edmöglicherweise nicht das richtige Werkzeug, siehe: bfs - Big File Scanner ).

# sample code to match lines with number 5 plus previous & following line
# (using Bash)
printf '%s\n' {1..20} > num.txt

# sed
sed -n '/5/=' num.txt | while read num; do
   n1=$((num - 1))
   n2=$((num + 1))
   [[ $n1 -lt 1 ]] && n1=1
   sed -n "${n1},${n2}p" num.txt
   echo --
done | sed -e '${/^--$/d;}'

# ed
cat <<-'EOF' | ed -s num.txt | sed -e $'N;N;a\\\n--' | sed -e '${/^--$/d;}'
H
0i
beginning: added line one
.
$a
end: added line one
.
,g/5/km\
'm-1,'m+1p
q
EOF
larz
quelle