Beim Durchlaufen von Dateien gibt es zwei Möglichkeiten:
benutze einen
for
-loop:for f in *; do echo "$f" done
benutze
find
:find * -prune | while read f; do echo "$f" done
Angenommen, diese beiden Schleifen finden dieselbe Liste von Dateien. Was sind die Unterschiede zwischen diesen beiden Optionen in Bezug auf Leistung und Handhabung?
bash
shell-script
performance
rubo77
quelle
quelle
find
öffnet die gefundenen Dateien nicht. Das einzige, was ich hier in Bezug auf eine große Anzahl von Dateien sehen kann, ist ARG_MAX .read f
Lesen Sie die Antworten und Kommentare, aus denen hervorgeht, dass Dateinamen beim Lesen beschädigt werden (z. B. Namen mit führenden Leerzeichen). Auchfind * -prune
scheint einen sehr gewundenen Weg zu sagen , einfach zu sein ,ls -1
nicht wahr?find .
nicht seinfind *
.ls -l
ist eine schlechte Idee. Aber das Parsenls -1
(das ist1
keinl
) ist nicht schlimmer als das Parsenfind * -prune
. Beide schlagen bei Dateien mit Zeilenumbrüchen im Namen fehl.Antworten:
1.
Der erste:
nicht für Dateien genannt
-n
,-e
und Varianten wie-nene
und mit einigen bash - Implementierungen mit Dateinamen Schrägstriche enthalten.Der Zweite:
nicht noch mehr Fälle (Dateien genannt
!
,-H
,-name
, ,(
die mit Leerzeichen oder Zeilenumbrüche starten, Dateinamen oder Ende enthalten ...)Es ist die Shell, die sich erweitert
*
undfind
nichts anderes tut, als die Dateien zu drucken, die sie als Argumente erhält. Sie hättenprintf '%s\n'
stattdessen auch verwenden können, was, wieprintf
es eingebaut ist, auch den zu vielen Argumenten möglichen Fehler vermeidet .2.
Die Erweiterung von
*
ist sortiert, Sie können es etwas schneller machen, wenn Sie die Sortierung nicht benötigen. Inzsh
:oder einfach:
bash
Soweit ich das beurteilen kann, gibt es kein Äquivalent, weshalb Sie darauf zurückgreifen müssenfind
.3.
(oben unter Verwendung einer GNU / BSD-
-print0
Nicht-Standard-Erweiterung).Das beinhaltet immer noch das Aufrufen eines Suchbefehls und die Verwendung einer langsamen
while read
Schleife. Es ist daher wahrscheinlich langsamer als die Verwendung derfor
Schleife, es sei denn, die Liste der Dateien ist riesig.4.
Im Gegensatz zur Shell-Platzhaltererweiterung
find
wirdlstat
bei jeder Datei ein Systemaufruf ausgeführt, sodass es unwahrscheinlich ist, dass das Nichtsortieren dies kompensiert.Mit GNU / BSD
find
kann dies vermieden werden, indem die-maxdepth
Erweiterung verwendet wird, die eine Optimierung auslöst, bei der Folgendes gespeichert wirdlstat
:Da
find
die Ausgabe von Dateinamen beginnt, sobald sie gefunden wurden (mit Ausnahme der Stdio-Ausgabepufferung), ist es möglicherweise schneller, wenn die Ausführung in der Schleife zeitaufwändig ist und die Liste der Dateinamen mehr als ein Stdio-Puffer ist (4) / 8 kB). In diesem Fall beginnt die Verarbeitung in der Schleife, bevorfind
alle Dateien gefunden wurden. Auf GNU- und FreeBSD-Systemen kann dies möglicherweisestdbuf
schneller geschehen (Deaktivieren der Stdio-Pufferung).5.
Die POSIX / standard / portable-Methode zum Ausführen von Befehlen für jede Datei
find
besteht darin, das-exec
Prädikat zu verwenden:In diesem Fall
echo
ist dies jedoch weniger effizient als das Schleifen in der Shell, da die Shell über eine integrierte Version vonecho
while verfügtfind
, die einen neuen Prozess erzeugen und/bin/echo
in jeder Datei ausführen muss.Wenn Sie mehrere Befehle ausführen müssen, haben Sie folgende Möglichkeiten:
Aber Vorsicht, das
cmd2
wird nur ausgeführt, wenncmd1
es erfolgreich ist.6.
Eine kanonische Möglichkeit, komplexe Befehle für jede Datei auszuführen, ist der Aufruf einer Shell mit
-exec ... {} +
:Diesmal sind wir wieder effizient,
echo
da wir diesh
eingebaute-exec +
Version verwenden und die Version so wenigsh
wie möglich erscheint.7.
In meinen Tests mit einem Verzeichnis mit 200.000 Dateien mit Kurznamen auf ext4 ist das Verzeichnis
zsh
(Absatz 2) bei weitem das schnellste, gefolgt von der ersten einfachenfor i in *
Schleife (obwohl dies wie üblichbash
viel langsamer ist als andere Shells).quelle
!
Befehl find?!
ist für die Verneinung.! -name . -prune more...
wird für jede Datei aber tun-prune
(und gibtmore...
da-prune
immer true zurück).
. So wird esmore...
für alle Dateien in tun.
, aber wird ausschließen.
und wird nicht in Unterverzeichnisse von absteigen.
. Es ist also das Standardäquivalent zu GNUs-mindepth 1 -maxdepth 1
.Ich habe dies in einem Verzeichnis mit 2259 Einträgen versucht und den
time
Befehl verwendet.Die Ausgabe von
time for f in *; do echo "$f"; done
(abzüglich der Dateien!) Ist:Die Ausgabe von
time find * -prune | while read f; do echo "$f"; done
(abzüglich der Dateien!) Ist:Ich habe jeden Befehl mehrmals ausgeführt, um Cache-Fehler zu vermeiden. Dies legt nahe, dass das Speichern in
bash
(für i in ...) schneller ist als das Verwendenfind
und Weiterleiten der Ausgabe (anbash
).Der Vollständigkeit halber habe ich die Pipe weggelassen
find
, da sie in Ihrem Beispiel völlig überflüssig ist. Die Ausgabe von justfind * -prune
ist:Außerdem
time echo *
(Ausgabe ist nicht durch Zeilenumbrüche getrennt, leider):An dieser Stelle vermute ich, dass der Grund
echo *
dafür, dass weniger Zeilenumbrüche ausgegeben werden, eher darin besteht, dass die Ausgabe nicht so stark scrollt. Lass uns testen ...ergibt:
während
time find * -prune > /dev/null
ergibt:und
time for f in *; do echo "$f"; done > /dev/null
ergibt:und schließlich:
time echo * > /dev/null
ergibt:Einige der Abweichungen können durch zufällige Faktoren erklärt werden, aber es scheint klar zu sein:
for f in *; do ...
ist langsamer alsfind * -prune
für sich allein, aber für die obigen Konstruktionen mit Rohren ist es schneller.Abgesehen davon scheinen beide Ansätze Namen mit Leerzeichen gut zu handhaben.
BEARBEITEN:
Timings für
find . -maxdepth 1 > /dev/null
vs.find * -prune > /dev/null
:time find . -maxdepth 1 > /dev/null
:find * -prune > /dev/null
:Also, zusätzliche Schlussfolgerung:
find * -prune
ist langsamer alsfind . -maxdepth 1
- im ersten Fall verarbeitet die Shell einen Globus und erstellt dann eine (große) Befehlszeile fürfind
. NB:find . -prune
kehrt gerade zurück.
.Weitere Tests
time find . -maxdepth 1 -exec echo {} \; >/dev/null
:Fazit:
quelle
find * -prune | while read f; do echo "$f"; done
hat die redundante Pipe - die Pipe gibt nur genau das aus, was sie selbstfind
ausgibt. Ohne Pipe wäre es einfach.find * -prune
Die Pipe ist nur redundant, weil das Ding auf der anderen Seite der Pipe einfach stdin nach stdout kopiert (zum größten Teil). Es ist eine teure No-Op. Wenn Sie etwas mit der Ausgabe von find anfangen wollen, außer es einfach wieder auszuspucken, ist das etwas anderes.*
. Wie BitsOfNix erklärte: schlage ich nicht vor stark zu verwenden*
und.
fürfind
statt.find . -prune
schneller ist, dafind
ein Verzeichniseintrag wörtlich gelesen wird, während die Shell dies ebenfalls tut und möglicherweise mit dem Glob übereinstimmt (möglicherweise für optimiert*
) und dann die große Befehlszeile für erstelltfind
.find . -prune
druckt nur.
auf meinem System. Es macht fast gar keine Arbeit. Es ist überhaupt nicht dasselbe, alsfind * -prune
dass alle Namen im aktuellen Verzeichnis angezeigt werden. Ein Nackterread f
wird Dateinamen mit führenden Leerzeichen entstellen.Ich würde auf jeden Fall mit find gehen, obwohl ich Ihren Fund so ändern würde:
In Bezug auf die Leistung
find
ist dies natürlich viel schneller, je nach Ihren Anforderungen. Was Sie gerade dabei habenfor
, zeigt nur die Dateien / Verzeichnisse im aktuellen Verzeichnis an, nicht jedoch den Inhalt der Verzeichnisse. Wenn Sie find verwenden, wird auch der Inhalt der Unterverzeichnisse angezeigt.Ich sage, find ist besser, da mit Ihrem zuerst
for
der*
Wille erweitert werden muss und ich befürchte, dass, wenn Sie ein Verzeichnis mit einer großen Menge von Dateien haben, es die Fehlerargumentliste zu lang geben könnte . Gleiches gilt fürfind *
In einem der Systeme, die ich derzeit verwende, gibt es beispielsweise einige Verzeichnisse mit mehr als 2 Millionen Dateien (jeweils <100.000):
quelle
-prune
, um die beiden Beispiele gleich zu machen. und ich bevorzuge die Pipe mit while, damit es einfacher ist, mehr Befehle in der Schleifeist eine nutzlose Verwendung von "
find
- Was Sie sagen, ist effektiv".*
Finden Sie für jede Datei im Verzeichnis ( ) keine Dateien. Außerdem ist es aus mehreren Gründen nicht sicher:-r
Option behandeltread
. Dies ist kein Problem mit derfor
Schleife.for
Schleife.Es
find
ist schwierig , mit Dateinamen umzugehen. Verwenden Sie daher diefor
Option loop, wenn immer dies möglich ist. Außerdem ist das Ausführen eines externen Programms wiefind
im Allgemeinen langsamer als das Ausführen eines internen Schleifenbefehls wiefor
.quelle
find
's-print0
nochxargs
'-0
sind POSIX-kompatibel, und Sie können keine willkürlichen Befehle eingebensh -c ' ... '
(einfache Anführungszeichen können nicht in einfache Anführungszeichen eingeschlossen werden), daher ist dies nicht ganz so einfach.Aber wir sind Trottel für Leistungsfragen! Diese Versuchsanfrage geht von mindestens zwei Annahmen aus, die sie fürchterlich ungültig machen.
A. Angenommen, sie finden dieselben Dateien…
Nun, sie werden zuerst dieselben Dateien finden, weil sie beide über denselben Glob iterieren, nämlich
*
. Esfind * -prune | while read f
weist jedoch einige Fehler auf, die möglicherweise dazu führen, dass nicht alle erwarteten Dateien gefunden werden:find
Implementierungen tun dies, aber Sie sollten sich trotzdem nicht darauf verlassen.find *
kann brechen, wenn Sie schlagenARG_MAX
.for f in *
wird nicht, daARG_MAX
giltexec
, nicht eingebaut.while read f
Kann mit Dateinamen brechen, die mit Leerzeichen beginnen und enden, die dann entfernt werden. Sie könnten dies mitwhile read
und seinem Standardparameter überwindenREPLY
, aber das hilft Ihnen immer noch nicht, wenn es um Dateinamen mit Zeilenumbrüchen geht.B
echo
.. Niemand wird dies tun, nur um den Namen der Datei wiederzugeben. Wenn Sie das möchten, führen Sie einfach einen der folgenden Schritte aus:Die Pipe zur
while
Schleife erzeugt hier eine implizite Subshell, die sich schließt, wenn die Schleife endet, was für manche uninteressant sein kann.Um die Frage zu beantworten, sind hier die Ergebnisse in einem Verzeichnis von mir, das 184 Dateien und Verzeichnisse enthält.
quelle
$ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20811 pts/1 R+ 0:00 grep bash $ while true; do while true; do while true; do while true; do while true; do sleep 100; done; done; done; done; done ^Z [1]+ Stopped sleep 100 $ bg [1]+ sleep 100 & $ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20924 pts/1 S+ 0:00 grep bash
find *
funktioniert nicht richtig, wenn*
Tokens erzeugt werden, die eher wie Prädikate als wie Pfade aussehen.Sie können das Problem nicht mit dem üblichen
--
Argument beheben, da--
es das Ende von Optionen angibt und die Optionen von find vor den Pfaden stehen.Um dieses Problem zu beheben, können Sie
find ./*
stattdessen verwenden. Aber dann produziert es nicht genau die gleichen Saiten wiefor x in *
.Beachten Sie, dass die
find ./* -prune | while read f ..
Scanfunktion von nicht verwendet wirdfind
. Es ist die Globbing-Syntax,./*
die das Verzeichnis tatsächlich durchläuft und Namen generiert. Dann muss dasfind
Programm mindestensstat
jeden dieser Namen überprüfen. Sie müssen das Programm starten, auf diese Dateien zugreifen und dann die E / A-Vorgänge ausführen, um die Ausgabe zu lesen.Es ist schwer vorstellbar, wie es alles andere als weniger effizient sein könnte
for x in ./* ...
.quelle
Gut für den Anfang
for
ist ein Shell-Schlüsselwort, das in Bash integriert ist, währendfind
es sich um eine separate ausführbare Datei handelt.Die
for
Schleife findet die Dateien des Globstar-Zeichens nur, wenn sie erweitert wird. Sie wird nicht in die gefundenen Verzeichnisse zurückgeführt.Auf der anderen Seite erhält Find auch eine vom Globstar erweiterte Liste, findet jedoch rekursiv alle Dateien und Verzeichnisse unter dieser erweiterten Liste und leitet sie jeweils an die
while
Schleife weiter.Beide Vorgehensweisen können als gefährlich angesehen werden, da sie keine Pfade oder Dateinamen verarbeiten, die Leerzeichen enthalten.
Das ist alles, was ich mir vorstellen kann, diese beiden Ansätze zu kommentieren.
quelle
Wenn alle von find zurückgegebenen Dateien mit einem einzigen Befehl verarbeitet werden können (gilt natürlich nicht für das obige Echo-Beispiel), können Sie xargs verwenden:
quelle
Ich benutze das seit Jahren: -
um nach bestimmten Dateien zu suchen (z. B. * .txt), die ein Muster enthalten, nach dem grep suchen kann, und um es weiterzuleiten, damit es nicht vom Bildschirm rollt. Manchmal benutze ich die >> -Pipe, um die Ergebnisse in eine andere Datei zu schreiben, die ich mir später ansehen kann.
Hier ist ein Beispiel des Ergebnisses:
quelle