Ich habe eine einzigartige Situation, in der ich die Lösungen, die auf dieser Seite vorgeschlagen wurden, benchmarken kann, und deshalb schreibe ich diese Antwort als Zusammenfassung der vorgeschlagenen Lösungen mit eingebundenen Laufzeiten für jede.
Einrichtung
Ich habe eine 3,261 Gigabyte ASCII-Textdatei mit einem Schlüssel-Wert-Paar pro Zeile. Die Datei enthält insgesamt 3.339.550.320 Zeilen und lässt sich in keinem der Editoren öffnen, die ich ausprobiert habe, einschließlich meinem bevorzugten Vim. Ich muss diese Datei subsetten, um einige der Werte, die ich entdeckt habe und die erst ab ungefähr Zeile ~500.000.000 beginnen, zu untersuchen.
Weil die Datei so viele Zeilen hat:
- Ich muss nur einen Teil der Zeilen extrahieren, um etwas Nützliches mit den Daten anzufangen.
- Das Durchlesen jeder Zeile, die zu den Werten führt, die mich interessieren, dauert lange.
- Wenn die Lösung über die Zeilen hinaus liest, die mich interessieren, und weiterhin die restlichen Zeilen der Datei liest, verschwendet sie Zeit damit, fast 3 Milliarden irrelevante Zeilen zu lesen und dauert 6-mal länger als nötig.
Mein Best-Case-Szenario ist eine Lösung, die nur eine einzige Zeile aus der Datei extrahiert, ohne irgendwelche anderen Zeilen in der Datei zu lesen, aber ich weiß nicht, wie ich das in Bash erreichen könnte.
Zum Wohl meiner geistigen Gesundheit werde ich nicht versuchen, die vollständigen 500.000.000 Zeilen zu lesen, die ich für mein eigenes Problem benötigen würde. Stattdessen werde ich versuchen, Zeile 50.000.000 von 3.339.550.320 zu extrahieren (was bedeutet, dass das Lesen der gesamten Datei 60-mal länger dauern wird als nötig).
Ich werde das eingebaute time
verwenden, um jeden Befehl zu benchmarken.
Ausgangspunkt
Lassen Sie uns zunächst sehen, wie die head
tail
Lösung aussieht:
$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0
real 1m15.321s
Die Ausgangsbasis für Zeile 50 Millionen beträgt 00:01:15.321, wenn ich direkt zu Zeile 500 Millionen gegangen wäre, wäre es wahrscheinlich ~12,5 Minuten.
cut
Ich bin skeptisch bei dieser, aber es ist einen Versuch wert:
$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0
real 5m12.156s
Dies dauerte 00:05:12.156, was viel langsamer ist als der Ausgangspunkt! Ich bin mir nicht sicher, ob es die gesamte Datei gelesen hat oder nur bis zur Zeile 50 Millionen bevor es aufgehört hat, aber unabhängig davon scheint dies keine praktikable Lösung für das Problem zu sein.
AWK
Ich habe die Lösung nur mit dem exit
ausgeführt, weil ich nicht auf das gesamte Ausführen der Datei warten wollte:
$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0
real 1m16.583s
Dieser Code lief in 00:01:16.583, was nur ~1 Sekunde langsamer ist, aber immer noch keine Verbesserung gegenüber der Ausgangsbasis darstellt. Bei diesem Tempo hätte es wahrscheinlich rund ~76 Minuten gedauert, die gesamte Datei zu lesen, wenn der Exit-Befehl ausgeschlossen worden wäre!
Perl
Ich habe auch die vorhandene Perl-Lösung ausgeführt:
$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0
real 1m13.146s
Dieser Code lief in 00:01:13.146, was ~2 Sekunden schneller ist als die Ausgangsbasis. Wenn ich es auf den vollständigen 500.000.000 Zeilen ausgeführt hätte, hätte es wahrscheinlich ~12 Minuten gedauert.
sed
Die beste Antwort im Board, hier ist mein Ergebnis:
$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0
real 1m12.705s
Dieser Code lief in 00:01:12.705, was 3 Sekunden schneller ist als die Ausgangsbasis und ~0,4 Sekunden schneller als Perl. Wenn ich es auf den vollständigen 500.000.000 Zeilen ausgeführt hätte, hätte es wahrscheinlich ~12 Minuten gedauert.
mapfile
Ich habe Bash 3.1 und kann daher die mapfile-Lösung nicht testen.
Fazit
Es scheint, dass es größtenteils schwierig ist, die head
tail
Lösung zu verbessern. Am besten bietet die sed
Lösung eine ~3%ige Steigerung der Effizienz.
(Prozentsätze berechnet mit der Formel % = (Laufzeit/Ausgangsbasis - 1) * 100
)
Zeile 50.000.000
- 00:01:12.705 (-00:00:02.616 = -3,47%)
sed
- 00:01:13.146 (-00:00:02.175 = -2,89%)
perl
- 00:01:15.321 (+00:00:00.000 = +0,00%)
head|tail
- 00:01:16.583 (+00:00:01.262 = +1,68%)
awk
- 00:05:12.156 (+00:03:56.835 = +314,43%)
cut
Zeile 500.000.000
- 00:12:07.050 (-00:00:26.160)
sed
- 00:12:11.460 (-00:00:21.750)
perl
- 00:12:33.210 (+00:00:00.000)
head|tail
- 00:12:45.830 (+00:00:12.620)
awk
- 00:52:01.560 (+00:40:31.650)
cut
Zeile 3.338.559.320
- 01:20:54.599 (-00:03:05.327)
sed
- 01:21:24.045 (-00:02:25.227)
perl
- 01:23:49.273 (+00:00:00.000)
head|tail
- 01:25:13.548 (+00:02:35.735)
awk
- 05:47:23.026 (+04:24:26.246)
cut
11 Stimmen
Die "Unix-Methode" besteht darin, Tools miteinander zu verketten, die ihre jeweilige Aufgabe gut erledigen. Ich denke also, du hast bereits eine sehr geeignete Methode gefunden. Andere Methoden umfassen
awk
undsed
und ich bin mir sicher, dass jemand auch mit einer Perl-One-Liner oder so etwas aufkommen kann ;)4 Stimmen
Der Doppelbefehl legt nahe, dass die Lösung
head | tail
nicht optimal ist. Es wurden andere, fast optimale Lösungen vorgeschlagen.0 Stimmen
Haben Sie irgendwelche Benchmarks durchgeführt, um herauszufinden, welche Lösung im Durchschnittsfall am schnellsten ist?
8 Stimmen
Leistungstests (für einen Bereich) unter Katze Zeile X bis Zeile Y auf einer riesigen Datei auf Unix & Linux. (cc @Marcin, falls du nach über zwei Jahren immer noch darüber nachdenkst)
13 Stimmen
Die
head | tail
-Lösung funktioniert nicht, wenn Sie eine Zeile abfragen, die nicht in der Eingabe existiert: Es wird die letzte Zeile gedruckt.0 Stimmen
Eine Lösung, die sicherlich nicht schneller ist als eines der dedizierten Tools, die in den bereits vorhandenen Antworten erwähnt wurden (deshalb füge ich sie als Kommentar hinzu), aber die für sehr knappe eingebettete Systeme nützlich sein kann, die möglicherweise wenige oder keine Textverarbeitungswerkzeuge haben, besteht aus einer Schleife, die nur POSIX-kompatible Shell-Built-Ins benötigt (NUM ist die gewünschte Zeile):
NUM=1000; while read X; do test "$NUM" -eq "0" && echo "$X" && break; NUM=$((NUM - 1)); done < filename
0 Stimmen
Erstellen Sie ein Skript
Verwendung: nth-Zeile [Datei]
, wenn die Datei ausgelassen wird, wird stdin betrachtet: github.com/BuonOmo/dotfiles/blob/main/.zsh/custom/functions/nth0 Stimmen
head -n$NN datei | tail -1
kann länger dauern, aber dieser NIX-Logik kann auch das umgekehrte Problem lösen:tail -n$NN datei | head -1
gibt dir die NN-te Zeile von hinten aus der Datei, währendsed
oderawk
auch etwas Arithmetik erfordern.