Ogranicz kontekst grep do N znaków w linii


31

Muszę przeszukiwać niektóre pliki JSON, w których długość linii przekracza kilka tysięcy znaków. Jak mogę ograniczyć grep do wyświetlania kontekstu do N znaków po lewej i prawej stronie dopasowania? Każde narzędzie inne niż grep byłoby również w porządku, o ile jest dostępne w popularnych pakietach Linux.

Byłby to przykładowy wynik dla wyimaginowanego przełącznika grep Ф :

$ grep -r foo *
hello.txt: Once upon a time a big foo came out of the woods.

$ grep -Ф 10 -r foo *
hello.txt: ime a big foo came of t



3
Nie duplikat. To około ± znaków, ale sugerowana alternatywa to około ± linii. (Odniesienia do stackoverflow jest dobre, choć.)
roaima

Odpowiedzi:


22

Z GNU grep:

N=10; grep -roP ".{0,$N}foo.{0,$N}" .

Wyjaśnienie:

  • -o => Drukuj tylko to, co pasowałeś
  • -P => Użyj wyrażeń regularnych w stylu Perla
  • Wyrażenie regularne mówi dopasuj 0 do $Nznaków, a foonastępnie 0 do $Nznaków.

Jeśli nie masz GNU grep:

find . -type f -exec \
    perl -nle '
        BEGIN{$N=10}
        print if s/^.*?(.{0,$N}foo.{0,$N}).*?$/$ARGV:$1/
    ' {} \;

Wyjaśnienie:

Ponieważ nie możemy dłużej polegać na grepbyciu GNU grep, używamy finddo wyszukiwania plików rekurencyjnie ( -rdziałanie GNU grep). Dla każdego znalezionego pliku uruchamiamy fragment Perla.

Przełączniki Perla:

  • -n Czytaj plik linia po linii
  • -l Usuń nowy wiersz na końcu każdej linii i włóż go z powrotem podczas drukowania
  • -e Traktuj następujący ciąg jako kod

Fragment Perla robi w zasadzie to samo, co grep. Zaczyna się od ustawienia zmiennej $Nna liczbę znaków kontekstu, które chcesz. Te BEGIN{}środki to jest wykonywane tylko raz na początku realizacji nie raz dla każdego wiersza w każdym pliku.

Instrukcja wykonywana dla każdej linii ma wydrukować linię, jeśli zadziała podstawienie wyrażenia regularnego.

Wyrażenie regularne:

  • Dopasuj dowolne stare leniwie 1 na początku wiersza ( ^.*?), a następnie .{0,$N}jak w grepprzypadku, a foonastępnie kolejne .{0,$N}i ostatecznie dopasuj dowolne stare rzeczy leniwie do końca wiersza ( .*?$).
  • Zastępujemy to $ARGV:$1. $ARGVto magiczna zmienna przechowująca nazwę czytanego pliku. $1jest to, co pasowały parens: kontekst w tym przypadku.
  • Leniwe mecze na obu końcach są wymagane, ponieważ chciwe dopasowanie zjadłoby wszystkie postacie wcześniej foobez niepowodzenia dopasowania (ponieważ .{0,$N}dozwolone jest dopasowanie zero razy).

1 Oznacza to, że wolę nie dopasowywać niczego, chyba że spowodowałoby to niepowodzenie ogólnego dopasowania. Krótko mówiąc, dopasuj jak najmniej znaków.


To miłe, dziękuję. Ma to tę wadę, że wyróżnia całe wyjście, nie tylko szukany tekst, ale można to obejść, dodając | grep foodo końca (jednak tracąc podświetlanie nazwy pliku w tym procesie).
dotancohen

1
@dotancohen Chyba nie możesz wygrać ich wszystkich :)
Joseph R.

w / GNU grepmożesz określić dopasowanie kolorów / aplikacji na podstawie flag zastosowanych za pomocą zmiennych środowiskowych. więc może nawet mógłbyś wygrać je wszystkie (bez obietnic - nawet nie jestem pewien, czy to zadziała w tym przypadku), ale osobiście nie widzę tu znaczenia ... w każdym razie ... graj dalej.
mikeserv

Niezła odpowiedź. Tylko uwaga: używając zshnie jestem w stanie zmusić go do działania, przekazując N = 10 jak w przykładzie. Działa to jednak, jeśli export N=10uruchomię to polecenie. Masz pomysł, jak dostosować przykład do pracy z zsh?
Gabe Kopley,

Lubperl -lne 'print "$ARGV: $_" for /.{0,10}foo.{0,10}/g'
Stéphane Chazelas

20

Spróbuj użyć tego:

grep -r -E -o ".{0,10}wantedText.{0,10}" *

-E mówi, że chcesz użyć rozszerzonego wyrażenia regularnego

-o mówi, że chcesz wydrukować tylko dopasowanie

-r grep szuka wyniku rekurencyjnie w folderze

REGEX:

{0,10} mówi, ile dowolnych znaków chcesz wydrukować

. reprezentuje dowolną postać (sama postać nie była tutaj ważna, tylko jej liczba)

Edycja: Rozumiem, że Joseph zaleca prawie takie samo rozwiązanie jak ja: D


Dziękuję Ci. Mimo że jest to zasadniczo to samo rozwiązanie, budzi zaufanie, że jest to najlepsza metoda, gdy dwie osoby niezależnie to zalecają.
dotancohen

Nie ma za co, społeczność Unix po prostu musi współpracować,
oto

2
Chociaż są one podobne, przyjęta odpowiedź nie zadziałała dla mnie (nadal produkowałem długie linie), ale jedno to zrobiło. Trik z N = 10 nie działa z powłoką bash.
meesern

w cygwin -E jest znacznie szybszy niż -P.
Bob Stein

2

Zaczerpnięte z: http://www.topbug.net/blog/2016/08/18/truncate-long-matching-lines-of-grep-a-solution-that-preserves-color/ oraz https: // stackoverflow. com / a / 39029954/1150462

Sugerowane podejście ".{0,10}<original pattern>.{0,10}"jest całkowicie dobre, z tym wyjątkiem, że kolor wyróżniający jest często pomieszany. Utworzyłem skrypt o podobnym wyniku, ale kolor jest również zachowany:

#!/bin/bash

# Usage:
#   grepl PATTERN [FILE]

# how many characters around the searching keyword should be shown?
context_length=10

# What is the length of the control character for the color before and after the matching string?
# This is mostly determined by the environmental variable GREP_COLORS.
control_length_before=$(($(echo a | grep --color=always a | cut -d a -f '1' | wc -c)-1))
control_length_after=$(($(echo a | grep --color=always a | cut -d a -f '2' | wc -c)-1))

grep -E --color=always "$1" $2 | grep --color=none -oE ".{0,$(($control_length_before + $context_length))}$1.{0,$(($control_length_after + $context_length))}"

Zakładając, że skrypt jest zapisany jako grepl, grepl pattern file_with_long_linespowinien wyświetlać pasujące linie, ale z pasującym łańcuchem tylko 10 znaków.


0

Rurociągi standardowe cutz -bflagą; możesz poinstruować wyjście grep do bajtów od 1 do 400 na linię.

grep "foobar" * | cut -b 1-400
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.