Czy jest jakaś alternatywa dla przełączników grep -A -B -C (aby wydrukować kilka linii przed i po)?

10
grep -A 2 -B 3 

wypisuje 2 linie po łańcuchu grep i wypisuje 3 linie wcześniej.

grep -C 3

drukuje 3 linie przed i 3 linie po

Niestety grepużywam nie obsługuje tych opcji. Czy są dostępne alternatywne polecenia lub skrypt do symulacji tego? Używasz skryptów powłoki sed/ awk/ perl/?

Prashant Bhate
źródło
+1 Nie wiedziałem, że jest -Cprzełącznik.
Lazer
1
Zainstaluj grep GNU. Mówiąc bardziej ogólnie, kiedy pojawiła się tutaj nowa maszyna Sun, pierwszym krokiem w konfiguracji było to, co ktoś nazwał GNU > /usr/local. Programy GNU mają wiele bardzo przydatnych rozszerzeń i zostały zaprojektowane w celu uniknięcia arbitralnych ograniczeń (ale płacisz drogo za rozmiar, a czasem za wydajność). Wiele zastrzeżonych systemów ma „nieoficjalne” repozytoria pakietów z GNU i innymi narzędziami. „Partner” nie powie Ci o nich, nawet jeśli są one zarządzane przez dostawcę ...
vonbrand

Odpowiedzi:

6

Jednym z umiarkowanie brzydkich sposobów jest to

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

lub wymienić -cz -C NUMza NUMlinii kontekstu. Będzie jednak generować dodatkową wydajność. (Jeśli twoje diffwsparcie -u/ -U NUM, będzie czystsze.)

Jeśli diffnie masz -c/ -C/ -u, wciąż możesz to zrobić, ale są one dość brzydkie. Z drugiej strony, system, który diffnawet nie obsługuje -cprawdopodobnie też nie ma Perla.

geekozaur
źródło
To jest fajne, działa jak urok, chociaż musiałem użyć opcji -bitw, aby działało w przypadku plików generowanych przez system Windows.
Prashant Bhate,
Możesz wysłać stdin do diff i pominąć tymczasowe:grep -v pattern file | diff -c - file
Cascabel
5

ACK wymaga tylko Perl i obejmuje -A, -Boraz -Copcje, które działają jak grep jest. Używa składni wyrażenia regularnego Perla zamiast grepa, a sposób wybierania plików do wyszukiwania jest zupełnie inny. Możesz wypróbować tę -fopcję podczas korzystania z niej (która drukuje pliki, które będzie przeszukiwać bez szukania czegokolwiek).

Można go zainstalować jako pojedynczy skrypt, który nie wymaga modułów innych niż rdzeń. Po prostu upuść go w swoim ~/binkatalogu (lub gdziekolwiek indziej na PATH, do którego masz dostęp do zapisu) i upewnij się, że jest chmodwykonywalny.

cjm
źródło
Jego pole produkcja i dont Niestety mam wystarczająco dużo przywilej instalowania czegokolwiek, i nie mogę ryzykować, chociaż, dzięki za tym poradniku będę go zainstalować i wypróbować na moim domowym laptopie
Prashant Bhate
@Prashant, nie potrzebujesz roota, aby zainstalować ackna własny użytek.
cjm
Tak, ale nadal nie mogę go tam użyć, chociaż jest pewne, że ten skrypt pozostanie na zawsze w moim ~ / bin :)
Prashant Bhate
@Prashant: Dlaczego nie możesz go użyć? To tylko skrypt perla.
intuicyjnie,
1
Jego pole PRODUKCJA wymaga specjalnych zezwoleń bla bla bla ... aby zrobić na nim dowolną rzecz. i coś idzie nie tak, przychodzi mi na głowę;) i to nie jest tego warte :)
Prashant Bhate
5

Ten prosty skrypt perla grep -Ado pewnego stopnia emuluje

#!/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
}

Pamiętaj, że możesz dodać instrukcję użytkowania, aby skrypt był czytelny i użyteczny;)

USAGE:    $./grep-A.pl <pattern> <numLines> <filename> 
Vijay Anant
źródło
Fajnie, którą wersję perla muszę uruchomić?
Prashant Bhate,
Używam wersji 5.1.10.1, chyba perl 5 jest obecnie dość powszechny.
Vijay Anant,
ya to 5.8.8 i działa, świetnie, ale potrzebuję skryptu, który robi to, co -B robi
Prashant Bhate
Dobrze. Zmieniłbym jednak kolejność argumentów; grep-A 3 foowygląda o wiele bardziej naturalnie niż grep-A foo 3. :-)
musiphil
3

Możesz po prostu zainstalować GNU grep lub Ack (napisane w Perlu, rozumie wiele opcji GNU grep i więcej).

Jeśli wolisz trzymać się standardowych narzędzi i odrobiny skryptów, oto skrypt awk, który emuluje zachowanie GNU grep -Ai -Bopcji. Minimalnie przetestowane.

#!/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

Uruchom go grep-ac -vpattern=PATTERN -vbefore=NBEFORE -vafter=NAFTERtam, gdzie PATTERNjest wzorzec do wyszukania ( rozszerzone wyrażenie regularne z kilkoma dodatkami awk ) NBEFOREi NAFTERjest liczbą wierszy do wydrukowania odpowiednio przed i po dopasowaniu (domyślnie 0). Przykład:

<input_file grep-ac -vbefore=2 -vpattern='foo *bar'
Gilles „SO- przestań być zły”
źródło
Każde rozwiązanie przechowujące dane w tablicy nie wchodzi w rachubę ... jak już wspomniałem, rozmiar pliku jest dość ogromny i może się przepełnić. Również awk w tym systemie nie pozwala na rozmiar pliku większy niż 3000 bajtów.
Prashant Bhate,
2
@Prashant: Nie rozumiem twoich zastrzeżeń. Ten skrypt usuwa wiersze, gdy nie kwalifikują się jako wiersze poprzedzające. Nie zużywa więcej pamięci, niż jest to z konieczności konieczne, biorąc pod uwagę wymagania, z wyjątkiem tego, że awk może mieć większy narzut niż program specjalnego przeznaczenia (ale mniej niż Perl, co również rozważasz). Całkowity rozmiar pliku jest całkowicie nieistotny.
Gilles 'SO - przestań być zły'
2
{ "exec" "awk" "-f" "$0" "$@"; }: bardzo fajny sposób na ominięcie ograniczeń w parsowaniu linii shebang.
dubiousjim
2

Okazuje się, że emulowanie opcji -B jest dość trudne z powodu problemów, które pojawiają się, gdy masz pasujące linie bezpośrednio po sobie. To w zasadzie uniemożliwia korzystanie z jakiegokolwiek rodzaju skanowania plików z pojedynczym przejściem.

Uświadomiłem sobie to podczas zabawy z następującym przybliżeniem:

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

Działa to mniej więcej poprawnie, jak grep -A7 -B3, z zastrzeżeniem opisanym w pierwszym akapicie.

Alternatywnym rozwiązaniem (również z jednym plikiem) tego problemu jest użycie perla do podania ciągu polecenia:

sed -n `perl -pe '$_=(/search_term/?sprintf("%d,%dp;", $.-3,$.+4):"")' file` file
użytkownik455
źródło
dość długi oneliner, ale ten plik jest bardzo duży, więc wypychanie linii do tablicy w tym przypadku jest złym pomysłem, prawda?
Prashant Bhate,
shift @A if push(@A,$_)>7;Nieco utrzymuje jedynie tablicę maksymalny rozmiar 7 dookoła. (to jest twój parametr -A). Druga opcja utrzymuje niewiarygodnie mały plik (po prostu uruchom perl bez zewnętrznej warstwy sed, aby zobaczyć, co tam jest generowane), ale odczytuje plik dwa razy.
user455
0

Za pomocą tego sedmożesz najpierw uzyskać numery pasujących linii, zmniejszyć i zwiększyć dany numer linii w whilepętli, a następnie użyć sed -n "n1,n2p"do wydrukowania linii kontekstu wiodącego ( n1) i trailing ( n2) (podobnie do sedalternatywy sugerowanej przez user455). Wiele procesów odczytu może jednak doprowadzić do spadku wydajności.

edmoże bezpośrednio odwoływać się do poprzednich i następnych wierszy dopasowanej linii, ale kończy się niepowodzeniem, jeśli określony zakres linii nie istnieje; na przykład pasująca linia to linia nr 2, ale należy wydrukować 5 linii przedmeczowych. Korzystanie edKonieczne jest zatem, aby dodać odpowiednią liczbę linii (pusty) na początku i na końcu. (W przypadku dużych plików edmoże nie być to właściwe narzędzie, patrz: bfs - skaner dużych plików ).

# 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
źródło