Gibt es einen "kanonischen" Weg, das zu tun? Ich habe verwendet, head -n | tail -1
was den Trick macht, aber ich habe mich gefragt, ob es ein Bash-Tool gibt, das speziell eine Zeile (oder einen Zeilenbereich) aus einer Datei extrahiert.
Mit "kanonisch" meine ich ein Programm, dessen Hauptfunktion dies ist.
awk
undsed
und ich bin sicher, dass jemand auch einen Perl-head | tail
Lösung nicht optimal ist. Andere nahezu optimale Lösungen wurden vorgeschlagen.head | tail
Lösung funktioniert nicht, wenn Sie eine Zeile abfragen, die in der Eingabe nicht vorhanden ist: Sie druckt die letzte Zeile.Antworten:
head
und Pipe mittail
wird für eine große Datei langsam sein. Ich würde so vorschlagensed
:Wo
NUM
ist die Nummer der Zeile, die Sie drucken möchten? Sosed '10q;d' file
drucken Sie beispielsweise die 10. Zeile vonfile
.Erläuterung:
NUMq
wird sofort beendet, wenn die Zeilennummer lautetNUM
.d
löscht die Zeile, anstatt sie zu drucken; Dies ist in der letzten Zeile gesperrt, da dadurch derq
Rest des Skripts beim Beenden übersprungen wird.Wenn Sie
NUM
eine Variable haben, möchten Sie doppelte Anführungszeichen anstelle von einfachen verwenden:quelle
sed -n 'NUMp'
undsed 'NUM!d'
Lösungen unten vorgeschlagen.tail -n+NUM file | head -n1
ist wahrscheinlich genauso schnell oder schneller. Zumindest war es auf meinem System (erheblich) schneller, als ich es mit NUM 250000 in einer Datei mit einer halben Million Zeilen versuchte. YMMV, aber ich verstehe nicht wirklich, warum es so wäre.cat
zwar schneller (fast doppelt so schnell), aber nur, wenn die Datei noch nicht zwischengespeichert wurde . Sobald die Datei zwischengespeichert ist , ist die direkte Verwendung des Dateinamenarguments schneller (etwa 1/3 schneller), während diecat
Leistung gleich bleibt. Seltsamerweise scheint unter OS X 10.9.3 nichts davon einen Unterschied zu machen:cat
/ neincat
, zwischengespeicherte Datei oder nicht. @anubhava: mein Vergnügen.sed 'NUMq
gibt die erstenNUM
Dateien aus und;d
löscht alle bis auf die letzte Zeile.druckt die 2. Zeile
2011th line
Zeile 10 bis Zeile 33
1. und 3. Zeile
und so weiter...
Um Zeilen mit sed hinzuzufügen, können Sie Folgendes überprüfen:
sed: Fügen Sie eine Linie an einer bestimmten Position ein
quelle
<
in diesem Fall nicht nötig. Es ist einfach meine Präferenz, Weiterleitungen zu verwenden, weil ich oft Weiterleitungen wiesed -n '100p' < <(some_command)
- also universelle Syntax :) verwendet habe. Es ist NICHT weniger effektiv, da die Umleitung mit der Shell erfolgt, wenn es sich selbst gabelt, also ... es ist nur eine Präferenz ... (und ja, es ist ein Zeichen länger) :)head
/tail
dassed -n '1p;3p'
Szenario nicht - auch bekannt als mehr nicht benachbarte Zeilen drucken ...Ich habe eine einzigartige Situation, in der ich die auf dieser Seite vorgeschlagenen Lösungen vergleichen kann. Daher schreibe ich diese Antwort als Konsolidierung der vorgeschlagenen Lösungen mit jeweils enthaltenen Laufzeiten.
Installieren
Ich habe eine 3,261-Gigabyte-ASCII-Textdatendatei mit einem Schlüssel-Wert-Paar pro Zeile. Die Datei enthält insgesamt 3.339.550.320 Zeilen und kann nicht in jedem Editor geöffnet werden, den ich ausprobiert habe, einschließlich meines Go-to-Vim. Ich muss diese Datei unterteilen, um einige der Werte zu untersuchen, die ich entdeckt habe. Beginnen Sie erst bei Zeile ~ 500.000.000.
Weil die Datei so viele Zeilen hat:
Mein Best-Case-Szenario ist eine Lösung, die nur eine einzelne Zeile aus der Datei extrahiert, ohne eine der anderen Zeilen in der Datei zu lesen, aber ich kann mir nicht vorstellen, wie ich dies in Bash erreichen würde.
Aus Gründen meiner geistigen Gesundheit werde ich nicht versuchen, die gesamten 500.000.000 Zeilen zu lesen, die ich für mein eigenes Problem benötige. Stattdessen werde ich versuchen, Zeile 50.000.000 aus 3.339.550.320 zu extrahieren (was bedeutet, dass das Lesen der vollständigen Datei 60x länger dauert als nötig).
Ich werde das
time
eingebaute verwenden, um jeden Befehl zu bewerten.Basislinie
Lassen Sie uns zuerst sehen, wie die
head
tail
Lösung:Die Basis für Zeile 50 Millionen ist 00: 01: 15.321. Wenn ich direkt für Zeile 500 Millionen gegangen wäre, wären es wahrscheinlich ~ 12,5 Minuten.
Schnitt
Ich bin zweifelhaft, aber es ist einen Versuch wert:
Dieser dauerte 00: 05: 12.156, was viel langsamer als die Grundlinie ist! Ich bin mir nicht sicher, ob es die gesamte Datei durchliest oder nur bis zu 50 Millionen Zeilen vor dem Stoppen, aber unabhängig davon scheint dies keine praktikable Lösung für das Problem zu sein.
AWK
Ich habe die Lösung nur mit ausgeführt,
exit
weil ich nicht auf die Ausführung der vollständigen Datei warten wollte:Dieser Code lief in 00: 01: 16.583, was nur ~ 1 Sekunde langsamer ist, aber immer noch keine Verbesserung der Basislinie darstellt. Bei dieser Geschwindigkeit hätte das Lesen der gesamten Datei wahrscheinlich ungefähr 76 Minuten gedauert, wenn der Exit-Befehl ausgeschlossen worden wäre!
Perl
Ich habe auch die vorhandene Perl-Lösung ausgeführt:
Dieser Code wurde in 00: 01: 13.146 ausgeführt, was ~ 2 Sekunden schneller als die Basislinie ist. Wenn ich es mit den vollen 500.000.000 laufen lassen würde, würde es wahrscheinlich ~ 12 Minuten dauern.
sed
Die beste Antwort an der Tafel, hier ist mein Ergebnis:
Dieser Code wurde in 00: 01: 12.705 ausgeführt, was 3 Sekunden schneller als die Basislinie und ~ 0,4 Sekunden schneller als Perl ist. Wenn ich es auf den vollen 500.000.000 Zeilen ausführen würde, hätte es wahrscheinlich ~ 12 Minuten gedauert.
Mapfile
Ich habe Bash 3.1 und kann daher die Mapfile-Lösung nicht testen.
Fazit
Es sieht zum größten Teil so aus, als ob es schwierig ist, die
head
tail
Lösung zu verbessern . Bestenfallssed
bietet die Lösung eine Effizienzsteigerung von ~ 3%.(Prozentsätze berechnet mit der Formel
% = (runtime/baseline - 1) * 100
)Zeile 50.000.000
sed
perl
head|tail
awk
cut
Zeile 500.000.000
sed
perl
head|tail
awk
cut
Zeile 3.338.559.320
sed
perl
head|tail
awk
cut
quelle
Damit
awk
geht es ziemlich schnell:Wenn dies zutrifft, wird das Standardverhalten von
awk
ausgeführt :{print $0}
.Alternative Versionen
Wenn Ihre Datei sehr groß ist, sollten Sie
exit
die gewünschte Zeile lesen. Auf diese Weise sparen Sie CPU-Zeit. Siehe Zeitvergleich am Ende der Antwort .Wenn Sie die Zeilennummer einer Bash-Variablen angeben möchten, können Sie Folgendes verwenden:
Sehen Sie, wie viel Zeit durch die Verwendung gespart wird
exit
, insbesondere wenn sich die Zeile zufällig im ersten Teil der Datei befindet:Der Unterschied beträgt also 0,198 Sekunden gegenüber 1,303 Sekunden und ist damit etwa sechsmal schneller.
quelle
awk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3
. Mit GNU awk kann dies mit beschleunigt werdenawk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3
.FS=RS
Feldspaltungen vermieden werden?FS=RS
keine Feldaufspaltung vermeiden, aber es parst nur die $ 0 Einsen und weist nur ein Feld , weil es keineRS
in$0
FS=RS
und keinen Unterschied in den Timings gesehen. Was ist mit mir, wenn ich eine Frage dazu stelle, damit Sie expandieren können? Vielen Dank!Nach meinen Tests lautet meine Empfehlung in Bezug auf Leistung und Lesbarkeit:
tail -n+N | head -1
N
ist die gewünschte Zeilennummer. Druckt beispielsweisetail -n+7 input.txt | head -1
die 7. Zeile der Datei.tail -n+N
druckt alles beginnend mit der ZeileN
und lässthead -1
es nach einer Zeile anhalten.Die Alternative
head -N | tail -1
ist vielleicht etwas besser lesbar. Dies druckt beispielsweise die 7. Zeile:head -7 input.txt | tail -1
Wenn es um die Leistung geht, gibt es keinen großen Unterschied für kleinere Größen, aber es wird von
tail | head
(von oben) übertroffen, wenn die Dateien riesig werden.Das Top-Voting
sed 'NUMq;d'
ist interessant zu wissen, aber ich würde argumentieren, dass es von weniger Leuten verstanden wird als die Head / Tail-Lösung und es ist auch langsamer als Tail / Head.In meinen Tests übertrafen beide Schwanz- / Kopfversionen
sed 'NUMq;d'
konstant. Dies steht im Einklang mit den anderen veröffentlichten Benchmarks. Es ist schwer, einen Fall zu finden, in dem Schwänze / Köpfe wirklich schlecht waren. Es ist auch nicht überraschend, da dies Vorgänge sind, von denen Sie erwarten würden, dass sie in einem modernen Unix-System stark optimiert werden.Um eine Vorstellung von den Leistungsunterschieden zu bekommen, ist dies die Nummer, die ich für eine große Datei (9,3 G) bekomme:
tail -n+N | head -1
: 3,7 Sekhead -N | tail -1
: 4,6 Seksed Nq;d
: 18,8 SekDie Ergebnisse können sich unterscheiden, aber die Leistung
head | tail
undtail | head
ist in der Regel für kleinere Eingänge vergleichbar undsed
ist immer langsamer um einen signifikanten Faktor (etwa 5 - fach oder so).Um meinen Benchmark zu reproduzieren, können Sie Folgendes versuchen, aber seien Sie gewarnt, dass eine 9.3G-Datei im aktuellen Arbeitsverzeichnis erstellt wird:
Hier ist die Ausgabe eines Laufs auf meinem Computer (ThinkPad X1 Carbon mit einer SSD und 16 GB Speicher). Ich gehe davon aus, dass im letzten Durchlauf alles aus dem Cache kommt, nicht von der Festplatte:
quelle
head | tail
vstail | head
? Oder hängt es davon ab, welche Zeile gedruckt wird (Dateianfang gegen Dateiende)?head -5 | tail -1
vstail -n+5 | head -1
. Eigentlich habe ich eine andere Antwort gefunden, die einen Testvergleich durchgeführt hat und sichtail | head
als schneller herausgestellt hat. stackoverflow.com/a/48189289Wow, alle Möglichkeiten!
Versuche dies:
oder eine davon, abhängig von Ihrer Version von Awk:
( Möglicherweise müssen Sie den Befehl
nawk
oder ausprobierengawk
.)Gibt es ein Werkzeug, mit dem nur diese bestimmte Zeile gedruckt wird? Keines der Standardwerkzeuge. Ist
sed
jedoch wahrscheinlich am nächsten und am einfachsten zu bedienen.quelle
Nützliche einzeilige Skripte für sed
quelle
Diese Frage ist mit Bash gekennzeichnet. Hier ist die Vorgehensweise für Bash (≥4): Verwenden Sie sie
mapfile
mit den Optionen-s
(Überspringen) und-n
(Zählen).Wenn Sie die 42. Zeile einer Datei benötigen
file
:Zu diesem Zeitpunkt haben Sie ein Array,
ary
dessen Felder die Zeilen vonfile
(einschließlich der nachfolgenden neuen Zeile) enthalten, in denen wir die ersten 41 Zeilen (-s 41
) übersprungen und nach dem Lesen einer Zeile (-n 1
) angehalten haben . Das ist also wirklich die 42. Zeile. So drucken Sie es aus:Wenn Sie einen Zeilenbereich benötigen, sagen Sie den Bereich 42–666 (einschließlich) und sagen Sie, dass Sie die Berechnung nicht selbst durchführen möchten, und drucken Sie sie auf stdout:
Wenn Sie diese Zeilen auch verarbeiten müssen, ist es nicht sehr praktisch, die nachfolgende neue Zeile zu speichern. Verwenden Sie in diesem Fall die
-t
Option (Trimmen):Sie können eine Funktion haben, die das für Sie erledigt:
Keine externen Befehle, nur eingebaute Bash!
quelle
Sie können auch sed print verwenden und beenden:
quelle
-n
Option deaktiviert die Standardaktion zum Drucken jeder Zeile, wie Sie sicherlich durch einen kurzen Blick auf die Manpage herausgefunden hätten.sed
allesed
Antworten ungefähr gleich schnell. Daher (für GNUsed
) ist dies die bestesed
Antwort, da dies Zeit für große Dateien und kleine n-te Zeilenwerte sparen würde .Sie können auch Perl dafür verwenden:
quelle
Die schnellste Lösung für große Dateien ist immer tail | head, vorausgesetzt, die beiden Entfernungen:
S
E
sind bekannt. Dann könnten wir dies verwenden:
Howmany ist nur die Anzahl der erforderlichen Zeilen.
Weitere Informationen finden Sie unter https://unix.stackexchange.com/a/216614/79743
quelle
S
undE
(dh Bytes, Zeichen oder Zeilen).Alle oben genannten Antworten beantworten die Frage direkt. Aber hier ist eine weniger direkte Lösung, aber eine möglicherweise wichtigere Idee, um zum Nachdenken anzuregen.
Da Leitungslängen beliebig sind, werden alle Bytes der Datei vor der n - ten Zeile Bedarf zu lesen. Wenn Sie eine große Datei haben oder diese Aufgabe viele Male wiederholen müssen und dieser Vorgang zeitaufwändig ist, sollten Sie ernsthaft darüber nachdenken, ob Sie Ihre Daten überhaupt anders speichern sollten.
Die eigentliche Lösung besteht darin, einen Index zu haben, z. B. am Anfang der Datei, der die Positionen angibt, an denen die Linien beginnen. Sie können ein Datenbankformat verwenden oder einfach eine Tabelle am Anfang der Datei hinzufügen. Alternativ können Sie eine separate Indexdatei erstellen, die Ihrer großen Textdatei beiliegt.
Sie können beispielsweise eine Liste von Zeichenpositionen für Zeilenumbrüche erstellen:
dann lesen Sie mit
tail
, was eigentlichseek
direkt zum entsprechenden Punkt in der Datei ist!zB um Zeile 1000 zu bekommen:
quelle
Als Folge der sehr hilfreichen Benchmarking-Antwort von CaffeineConnoisseur ... Ich war neugierig, wie schnell die 'Mapfile'-Methode mit anderen verglichen wurde (da diese nicht getestet wurde), und habe selbst einen schnellen und schmutzigen Geschwindigkeitsvergleich versucht Ich habe Bash 4 zur Hand. Wirf einen Test der "tail | head" -Methode (anstelle von head | tail) durch, die in einem der Kommentare zur Top-Antwort erwähnt wurde, als ich dabei war, während die Leute ihr Lob singen. Ich habe nicht annähernd die Größe der verwendeten Testdatei. Das Beste, was ich kurzfristig finden konnte, war eine 14-Millionen-Stammbaumdatei (lange Zeilen, die durch Leerzeichen getrennt sind, knapp 12000 Zeilen).
Kurzversion: Mapfile erscheint schneller als die Cut-Methode, aber langsamer als alles andere, also würde ich es einen Dud nennen. Schwanz | Kopf, OTOH, sieht so aus, als könnte es das schnellste sein, obwohl bei einer Datei dieser Größe der Unterschied im Vergleich zu sed nicht allzu groß ist.
Hoffe das hilft!
quelle
Mit dem, was andere erwähnten, wollte ich, dass dies eine schnelle und schnelle Funktion in meiner Bash-Shell ist.
Erstellen Sie eine Datei:
~/.functions
Fügen Sie den Inhalt hinzu:
getline() { line=$1 sed $line'q;d' $2 }
Dann füge dies zu deinem hinzu
~/.bash_profile
:source ~/.functions
Wenn Sie jetzt ein neues Bash-Fenster öffnen, können Sie die Funktion einfach so aufrufen:
getline 441 myfile.txt
quelle
Wenn Sie mehrere Zeilen haben, die durch \ n begrenzt sind (normalerweise neue Zeile). Sie können auch 'cut' verwenden:
Sie erhalten die 2. Zeile aus der Datei.
-f3
gibt Ihnen die 3. Zeile.quelle
cat FILE | cut -f2,5 -d$'\n'
Zeigt die Zeilen 2 und 5 der DATEI an. (Aber es wird die Ordnung nicht bewahren.)So drucken Sie die n-te Zeile mit sed mit einer Variablen als Zeilennummer:
Hier dient das '-e'-Flag zum Hinzufügen eines Skripts zum auszuführenden Befehl.
quelle
Viele gute Antworten schon. Ich persönlich gehe mit awk. Wenn Sie bash verwenden, fügen Sie der Einfachheit halber einfach das Folgende zu Ihrem hinzu
~/.bash_profile
. Und wenn Sie sich das nächste Mal anmelden (oder wenn Sie Ihr .bash_profile nach diesem Update als Quelle verwenden), steht Ihnen eine neue "n-te" Funktion zur Verfügung, mit der Sie Ihre Dateien weiterleiten können.Führen Sie dies aus oder fügen Sie es in Ihr ~ / .bash_profile ein (wenn Sie bash verwenden) und öffnen Sie bash erneut (oder führen Sie es aus
source ~/.bach_profile
).# print just the nth piped in line nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }
Um es zu benutzen, leiten Sie es einfach durch. Z.B,:
$ yes line | cat -n | nth 5 5 line
quelle
Nachdem ich mir die Top-Antwort und den Benchmark angesehen habe , habe ich eine winzige Hilfsfunktion implementiert:
Grundsätzlich können Sie es auf zwei Arten verwenden:
quelle
Ich habe einige der oben genannten Antworten in ein kurzes Bash-Skript eingefügt, das Sie in eine Datei mit dem Namen
get.sh
und einem Link zu/usr/local/bin/get
(oder einem anderen von Ihnen bevorzugten Namen) einfügen können.Stellen Sie sicher, dass es mit ausführbar ist
Verknüpfen Sie es, um es auf dem
PATH
mit verfügbar zu machenVerantwortungsvoll geniessen!
P.
quelle