Begrenzen Sie den Grep-Kontext auf N Zeichen in der Zeile


31

Ich muss einige JSON-Dateien durchgehen, in denen die Zeilenlängen einige tausend Zeichen überschreiten. Wie kann ich grep einschränken, um den Kontext bis zu N Zeichen links und rechts von der Übereinstimmung anzuzeigen? Jedes andere Tool als grep wäre ebenfalls in Ordnung, solange es in gängigen Linux-Paketen verfügbar ist.

Dies wäre eine Beispielausgabe für den imaginären grep-Schalter Ф :

$ 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
Kein Duplikat. Dies sind ungefähr ± Zeichen, aber Ihre vorgeschlagene Alternative sind ungefähr ± Zeilen. (Ihr Hinweis auf Stackoverflow ist jedoch gut.)
Roaima

Antworten:


22

Mit GNU grep:

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

Erläuterung:

  • -o => Nur das drucken, was du gefunden hast
  • -P => Verwenden Sie reguläre Ausdrücke im Perl-Stil
  • Der reguläre Ausdruck besagt, dass $NZeichen foomit 0 gefolgt von $NZeichen mit 0 übereinstimmen .

Wenn Sie kein GNU haben grep:

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

Erläuterung:

Da wir uns nicht mehr darauf verlassen können grep, GNU zu sein grep, findsuchen wir rekursiv nach Dateien (die -rAktion von GNU grep). Für jede gefundene Datei führen wir das Perl-Snippet aus.

Perl-Schalter:

  • -n Lesen Sie die Datei Zeile für Zeile
  • -l Entfernen Sie den Zeilenumbruch am Ende jeder Zeile und setzen Sie ihn beim Drucken wieder ein
  • -e Behandeln Sie die folgende Zeichenfolge als Code

Das Perl-Snippet macht im Wesentlichen dasselbe wie grep. Zunächst wird eine Variable $Nauf die Anzahl der gewünschten Kontextzeichen gesetzt. Die BEGIN{}Mittel , dies wird nur einmal zu Beginn der Ausführung nicht einmal für jede Zeile in jeder Datei ausgeführt.

Die Anweisung, die für jede Zeile ausgeführt wird, gibt die Zeile aus, wenn die Regex-Ersetzung funktioniert.

Der Regex:

  • Ordnen Sie ein beliebiges altes Ding faul 1 am Anfang der Zeile ( ^.*?) zu, gefolgt von .{0,$N}wie im grepFall, foogefolgt von einem anderen .{0,$N}und ordnen Sie schließlich ein beliebiges altes Ding faul bis zum Ende der Zeile ( .*?$) zu.
  • Wir ersetzen dies durch $ARGV:$1. $ARGVist eine magische Variable, die den Namen der aktuell gelesenen Datei enthält. $1Dazu passten die Eltern: der Kontext in diesem Fall.
  • Die faulen Übereinstimmungen an beiden Enden sind erforderlich, da eine gierige Übereinstimmung alle Zeichen zuvor frisst, fooohne dass eine Übereinstimmung fehlschlägt (da .{0,$N}null Übereinstimmungen zulässig sind).

1 Das heißt, passen Sie lieber nichts an, es sei denn, dies würde dazu führen, dass die Gesamtübereinstimmung fehlschlägt. Kurz gesagt, stimmen Sie mit so wenigen Zeichen wie möglich überein.


Sehr Schön. Danke. Dies hat den Nachteil, dass die gesamte Ausgabe hervorgehoben wird, nicht nur der gesuchte Text, sondern dass dies durch Anhängen | grep fooam Ende umgangen werden kann (wobei jedoch die Hervorhebung des Dateinamens verloren geht).
Dotancohen

1
@ Dotancohen Ich denke, Sie können nicht alle gewinnen :)
Joseph R.

Mit GNU können grepSie Match-Farben / -Anwendungen basierend auf Flags festlegen, die über Umgebungsvariablen angewendet werden. vielleicht so auch Sie können sie alle gewinnen (keine Versprechungen - nicht einmal sicher , wäre es in diesem Fall arbeiten) , aber ich habe nicht persönlich die Bedeutung hier sehen ... wie auch immer ... weiter spielen.
mikeserv

Gute Antwort. Nur eine Anmerkung, mit kann zshich nicht erreichen, dass es funktioniert, wenn N = 10 wie im Beispiel übergeben wird. Es funktioniert jedoch, wenn ich export N=10vor dem Ausführen des Befehls. Irgendeine Idee, wie man das Beispiel an die Arbeit mit zsh anpasst?
Gabe Kopley

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

19

Versuchen Sie diesen zu benutzen:

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

-E sagt, dass Sie erweiterte reguläre Ausdrücke verwenden möchten

-o sagt, dass Sie nur die Übereinstimmung drucken möchten

-r grep sucht rekursiv nach dem Ergebnis im Ordner

REGEX:

{0,10} gibt an , wie viele beliebige Zeichen gedruckt werden sollen

. stellt ein beliebiges Zeichen dar (ein Zeichen selbst war hier nicht wichtig, nur ihre Nummer)

Edit: Oh, ich sehe, dass Joseph fast die gleiche Lösung wie ich empfiehlt: D


Vielen Dank. Auch wenn es sich im Wesentlichen um dieselbe Lösung handelt, ist es zuversichtlich, dass dies die beste Methode ist, wenn zwei Personen sie unabhängig voneinander empfehlen.
Dotancohen


2
Obwohl sie ähnlich sind, hat die akzeptierte Antwort bei mir nicht funktioniert (immer noch lange Schlangen), aber eine davon hat funktioniert. Der Trick mit N = 10 funktioniert nicht mit einer Bash-Shell.
Meesern

in cygwin -E ist das deutlich schneller als -P.
Bob Stein

2

Entnommen aus: http://www.topbug.net/blog/2016/08/18/truncate-long-matching-lines-of-grep-a-solution-that-preserves-color/ und https: // stackoverflow. com / a / 39029954/1150462

Der vorgeschlagene Ansatz ".{0,10}<original pattern>.{0,10}"ist vollkommen gut, mit der Ausnahme, dass die Hervorhebungsfarbe oft durcheinander gebracht wird. Ich habe ein Skript mit einer ähnlichen Ausgabe erstellt, aber die Farbe bleibt auch erhalten:

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

Angenommen, das Skript wird gespeichert als grepl, dann grepl pattern file_with_long_linessollten die übereinstimmenden Zeilen mit nur 10 Zeichen um die übereinstimmende Zeichenfolge angezeigt werden.


0

Leiten von stdout zu cutmit der -bFlagge; Sie können die Ausgabe von grep auf die Bytes 1 bis 400 pro Zeile beschränken.

grep "foobar" * | cut -b 1-400
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.