Schneller Unix-Befehl zum Anzeigen bestimmter Zeilen in der Mitte einer Datei?


206

Der Versuch, ein Problem mit einem Server zu debuggen, und meine einzige Protokolldatei ist eine 20-GB-Protokolldatei (auch ohne Zeitstempel! Warum wird die System.out.println()Protokollierung verwendet? In der Produktion?!)

Mit grep habe ich einen Bereich der Datei gefunden, den ich mir ansehen möchte, Zeile 347340107.

Anders als so etwas zu tun

head -<$LINENUM + 10> filename | tail -20 

... was das headLesen der ersten 347 Millionen Zeilen der Protokolldatei erfordern würde. Gibt es einen schnellen und einfachen Befehl, mit dem die Zeilen 347340100 - 347340200 (zum Beispiel) an die Konsole ausgegeben werden?

Update Ich habe total vergessen, dass grep den Kontext um ein Match drucken kann ... das funktioniert gut. Vielen Dank!


Ich würde mir vorstellen, dass grep die gesamte Datei durchsuchen muss. Es muss eine weniger intensive CPU-Methode geben, um dies zu tun.
Ojblass

Antworten:


69

mit GNU-grep könnte man einfach sagen

grep --context = 10 ...

7
Oder genauer gesagt 10 Zeilen vor: grep -B 10 ... oder 10 Zeilen nach: grep -A 10 ...
Boy Baukema

17
Dieser Befehl funktioniert nicht, unter sed -n '<start>, <end> p' funktioniert
Basav

5
Dies ist eigentlich nicht das, was Sie wollen, da es die gesamte Datei verarbeitet, selbst wenn die Übereinstimmung im oberen Bit liegt. Zu diesem Zeitpunkt ist eine Kombination aus Kopf / Schwanz oder Schwanz / Kopf viel effektiver.
Sklivvz

3
Dies erfüllt die gestellte Frage überhaupt nicht, da dies keine Möglichkeit bietet, eine bestimmte Zeile wie gestellt auszugeben .
Chris Rasys

1
Dies ist eigentlich nicht das, was gefragt wurde. @matt b, warum akzeptierst du diese Antwort nicht?
user1271772

390

Ich habe zwei andere Lösungen gefunden, wenn Sie die Zeilennummer kennen, aber sonst nichts (kein grep möglich):

Angenommen, Sie benötigen die Zeilen 20 bis 40,

sed -n '20,40p;41q' file_name

oder

awk 'FNR>=20 && FNR<=40' file_name

6
+1: Möglicherweise möchten Sie jedoch nach dem Drucken beenden. Kann einige Leistungsvorteile bieten, wenn die Datei wirklich riesig ist.
Jaypal Singh

awk 'NR> = 20 && NR <= 40' Dateiname
Sudipta Basak

2
sed -n '20, 40p; 41q 'Dateiname zum Beenden dann.
Snigdha Batra

1
Dies sind insbesondere Start- und Endzeilennummern. Wenn Sie sich in einer größeren Datei befinden, lautet diese '12345678,12345699p'
Code Abominator

1
Zusätzlich zu @ CodeAbominators Kommentar weisen Sie 41qsed an , in der Zeile zu beenden41 .
Brice

116
# print line number 52
sed -n '52p' # method 1
sed '52!d' # method 2
sed '52q;d' # method 3,  efficient on large files 

Methode 3 effizient bei großen Dateien

schnellster Weg, um bestimmte Linien anzuzeigen


Ich versuche herauszufinden, wie Methode 3 angepasst werden kann, um einen Bereich anstelle einer einzelnen Zeile zu verwenden, aber ich befürchte, mein sed-foo ist der Aufgabe nicht gewachsen.
Xiong Chiamiov

9
@XiongChiamiov Wie wäre es mit sed -n '1.500p; 501q' zum Drucken von 1-500?
Sam

3
Der Grund, warum die ersten beiden Zeilen / Methoden weniger effizient sind, besteht darin, dass sie alle Zeilen nach Zeile 52 bis zum Ende weiter verarbeiten, während # 3 nach dem Drucken von Zeile 52 stoppt.
flow2k

1
Diese Antwort würde davon profitieren, zu erklären, was alle Argumente tun.
Bram Vanroy

25

Nein, gibt es nicht, Dateien sind nicht zeilenadressierbar.

Es gibt keine zeitkonstante Möglichkeit, den Zeilenanfang n in einer Textdatei zu finden . Sie müssen durch die Datei streamen und Zeilenumbrüche zählen.

Verwenden Sie das einfachste / schnellste Werkzeug, das Sie für die Arbeit benötigen. Für mich mit headmacht viel mehr Sinn als grep, da letztere ist viel komplizierter. Ich sage nicht " grepist langsam", das ist es wirklich nicht, aber ich wäre überrascht, wenn es schneller wäre als headin diesem Fall. Das wäre im headGrunde genommen ein Fehler .


2
Sofern Zeilen keine feste Breite in Bytes haben, wissen Sie nicht, wohin Sie den Dateizeiger verschieben sollen, ohne vom Anfang der Datei an neue Zeilenzeichen zu zählen.
Joseph Lust

Dies gibt keine Antwort auf die Frage. Um einen Autor zu kritisieren oder um Klarstellung zu bitten, hinterlassen Sie einen Kommentar unter seinem Beitrag.
Exhuma

@exhuma Du hast recht. Ich habe umgeschrieben. Vor sieben Jahren wurde ich verärgert. :)
entspannen Sie sich

20

Wie wäre es mit:

tail -n +347340107 filename | head -n 100

Ich habe es nicht getestet, aber ich denke, das würde funktionieren.


Nein, normalerweise hat der Schwanz je nach Version und Betriebssystem ein Limit von 256 letzten Kilobyte oder ähnlichem.
Antti Rytsölä

Ess yessire miller
dctremblay

13

Ich gehe lieber einfach in lessund

  • tippen, um 50%zur Hälfte der Datei zu gelangen,
  • 43210G zur Linie 43210 gehen
  • :43210 das Gleiche tun

und solche Sachen.

Noch besser: schlagen v Drücken Sie, um an dieser Stelle mit der Bearbeitung zu beginnen (natürlich in vim!). Beachten Sie nun, dass vimdie gleichen Tastenkombinationen vorhanden sind!


12

Ich würde die Datei zuerst in einige kleinere wie diese aufteilen

$ split --lines=50000 /path/to/large/file /path/to/output/file/prefix

und grep dann auf die resultierenden Dateien.


stimmte zu, brechen Sie die Anmeldung ab und erstellen Sie einen Cron-Job, um dies ordnungsgemäß zu tun. Verwenden Sie Logrotate oder ähnliches, um zu verhindern, dass sie so groß werden.
Tanj

9

Sie können den exBefehl verwenden, einen Standard-Unix-Editor (jetzt Teil von Vim), z

  • eine einzelne Zeile anzeigen (zB 2.):

    ex +2p -scq file.txt

    entsprechende sed-Syntax: sed -n '2p' file.txt

  • Zeilenbereich (zB 2-5 Zeilen):

    ex +2,5p -scq file.txt

    sed Syntax: sed -n '2,5p' file.txt

  • von der angegebenen Zeile bis zum Ende (zB 5. bis zum Ende der Datei):

    ex +5,p -scq file.txt

    sed Syntax: sed -n '2,$p' file.txt

  • mehrere Zeilenbereiche (z. B. 2-4 und 6-8 Zeilen):

    ex +2,4p +6,8p -scq file.txt

    sed Syntax: sed -n '2,4p;6,8p' file.txt

Die oben genannten Befehle können mit der folgenden Testdatei getestet werden:

seq 1 20 > file.txt

Erläuterung:

  • + oder -c gefolgt vom Befehl - Führen Sie den Befehl (vi / vim) aus, nachdem die Datei gelesen wurde.
  • -s - Silent-Modus, verwendet auch das aktuelle Terminal als Standardausgabe,
  • qgefolgt von -cdem Befehl zum Beenden des Editors (hinzufügen !, um das Beenden zu erzwingen, z -scq!. B. ).

6

Wenn Ihre Zeilennummer 100 zum Lesen ist

head -100 filename | tail -1

6

Bekommen ack

Ubuntu / Debian installieren:

$ sudo apt-get install ack-grep

Dann renne:

$ ack --lines=$START-$END filename

Beispiel:

$ ack --lines=10-20 filename

Von $ man ack:

--lines=NUM
    Only print line NUM of each file. Multiple lines can be given with multiple --lines options or as a comma separated list (--lines=3,5,7). --lines=4-7 also works. 
    The lines are always output in ascending order, no matter the order given on the command line.

1
Dies scheint mir der Befehl mit der intuitivsten Syntax aller Antworten hier zu sein.
nzn

Ab Version 2.999_06 vom 10. Januar 2019 wurde der --linesParameter entfernt.
Burny

4

sed muss auch die Daten lesen, um die Zeilen zu zählen. Die einzige Möglichkeit, eine Verknüpfung zu erstellen, besteht darin, dass die Datei einen Kontext / eine Reihenfolge enthält, mit der bzw. der gearbeitet werden soll. Wenn beispielsweise Protokollzeilen mit einer festen Breite für Uhrzeit / Datum usw. vorangestellt sind, können Sie das Dienstprogramm look unix verwenden, um die Dateien nach bestimmten Datums- / Uhrzeitangaben binär zu durchsuchen


4

Verwenden

x=`cat -n <file> | grep <match> | awk '{print $1}'`

Hier erhalten Sie die Zeilennummer, in der die Übereinstimmung stattgefunden hat.

Jetzt können Sie mit dem folgenden Befehl 100 Zeilen drucken

awk -v var="$x" 'NR>=var && NR<=var+100{print}' <file>

oder Sie können auch "sed" verwenden

sed -n "${x},${x+100}p" <file>

Wenn Sie mehr als eine Übereinstimmung haben, verwenden Sie: "awk 'NR == 1 {print $ 1}" für die erste Übereinstimmung und so weiter
Ramana Reddy

2

Wenn sed -e '1,N d; M q'Sie die Zeilen N + 1 bis M drucken, ist dies wahrscheinlich etwas besser, grep -Cda nicht versucht wird, die Linien einem Muster zuzuordnen.


-eist hier optional.
flow2k

2

Aufbauend auf der Antwort von Sklivvz ist hier eine nette Funktion, die man in eine .bash_aliasesDatei einfügen kann . Es ist effizient bei großen Dateien, wenn Inhalte von der Vorderseite der Datei gedruckt werden.

function middle()
{
    startidx=$1
    len=$2
    endidx=$(($startidx+$len))
    filename=$3

    awk "FNR>=${startidx} && FNR<=${endidx} { print NR\" \"\$0 }; FNR>${endidx} { print \"END HERE\"; exit }" $filename
}

1

Gehen Sie folgendermaßen vor, um eine Zeile von a <textfile>by its anzuzeigen <line#>:

perl -wne 'print if $. == <line#>' <textfile>

Wenn Sie eine leistungsfähigere Methode zum Anzeigen einer Reihe von Zeilen mit regulären Ausdrücken wünschen - ich werde nicht sagen, warum grep eine schlechte Idee dafür ist, sollte dies ziemlich offensichtlich sein -, zeigt Ihnen dieser einfache Ausdruck Ihre Reichweite in a Single Pass, was Sie wollen, wenn Sie mit ~ 20 GB Textdateien arbeiten:

perl -wne 'print if m/<regex1>/ .. m/<regex2>/' <filename>

(Tipp: Wenn Ihre Regex enthalten ist /, verwenden Sie m!<regex>!stattdessen etwas wie )

Dies würde <filename>beginnend mit der übereinstimmenden Zeile <regex1>bis (einschließlich) der übereinstimmenden Zeile ausgedruckt <regex2>.

Es braucht keinen Assistenten, um zu sehen, wie ein paar Änderungen es noch leistungsfähiger machen können.

Letzte Sache: Perl hat, da es eine ausgereifte Sprache ist, viele versteckte Verbesserungen, um Geschwindigkeit und Leistung zu fördern. In diesem Sinne ist dies die offensichtliche Wahl für einen solchen Vorgang, da er ursprünglich für die Verarbeitung großer Protokolldateien, Texte, Datenbanken usw. entwickelt wurde.


Wirklich, es scheint mir nicht so zu sein, denn wann wird ein Perl-Befehl ausgeführt, der komplizierter ist als etwa das Ausführen von 2+ Programmen, die zusammen geleitet werden (weiter unten auf der Seite), und ich denke, Sie sagen es tatsächlich, weil ich mehr eingegeben habe Eine Erklärung, die es erforderlich machte, dass Sie LESEN, da es auf der Seite ebenso komplexe (oder mehr) gibt, die nicht aus dem Wasser gesprengt wurden ... meine Güte
osirisgothra

Beachten Sie, dass der Benutzer nach einer Reihe von Zeilen gefragt hat - Ihr Beispiel kann jedoch trivial angepasst werden.
Sklivvz

0

Sie könnten diesen Befehl versuchen:

egrep -n "*" <filename> | egrep "<line number>"

0

Einfach mit Perl! Wenn Sie die Zeilen 1, 3 und 5 aus einer Datei abrufen möchten, sagen Sie / etc / passwd:

perl -e 'while(<>){if(++$l~~[1,3,5]){print}}' < /etc/passwd

1
Sie sagen, es ist einfach mit awk, aber Sie haben es stattdessen in Perl gemacht?
Gefangener 13.

0

Ich bin überrascht, dass nur eine andere Antwort (von Ramana Reddy) vorgeschlagen hat, der Ausgabe Zeilennummern hinzuzufügen. Im Folgenden wird nach der erforderlichen Zeilennummer gesucht und die Ausgabe gefärbt.

file=FILE
lineno=LINENO
wb="107"; bf="30;1"; rb="101"; yb="103"
cat -n ${file} | { GREP_COLORS="se=${wb};${bf}:cx=${wb};${bf}:ms=${rb};${bf}:sl=${yb};${bf}" grep --color -C 10 "^[[:space:]]\\+${lineno}[[:space:]]"; }

Antworten mit Code werden normalerweise nur zum Löschen markiert. Könnten Sie einen Kommentar dazu hinzufügen, wie dies das Problem löst?
Graham
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.