x=$(find . -name "*.txt")
echo $x
Wenn ich den obigen Code in der Bash-Shell ausführe, erhalte ich eine Zeichenfolge, die mehrere durch Leerzeichen getrennte Dateinamen enthält, keine Liste.
Natürlich kann ich sie weiter durch Leerzeichen trennen, um eine Liste zu erhalten, aber ich bin mir sicher, dass es einen besseren Weg gibt, dies zu tun.
Was ist der beste Weg, um die Ergebnisse eines find
Befehls zu durchlaufen ?
x=( $(find . -name "*.txt") ); echo "${x[@]}"
Dann können Sie durchschleifenfor item in "${x[@]}"; { echo "$item"; }
Antworten:
TL; DR: Wenn Sie nur hier sind, um die richtigste Antwort zu erhalten, möchten Sie wahrscheinlich meine persönliche Präferenz
find . -name '*.txt' -exec process {} \;
(siehe unten in diesem Beitrag). Wenn Sie Zeit haben, lesen Sie den Rest durch, um verschiedene Möglichkeiten und die Probleme mit den meisten von ihnen zu sehen.Die vollständige Antwort:
Der beste Weg hängt davon ab, was Sie tun möchten, aber hier sind einige Optionen. Solange keine Datei oder kein Ordner im Teilbaum Leerzeichen enthält, können Sie die Dateien einfach durchlaufen:
Etwas besser, schneiden Sie die temporäre Variable aus
x
:Es ist viel besser zu globieren, wenn du kannst. White-Space-Safe für Dateien im aktuellen Verzeichnis:
Durch Aktivieren der
globstar
Option können Sie alle übereinstimmenden Dateien in diesem Verzeichnis und in allen Unterverzeichnissen globalisieren:In einigen Fällen, z. B. wenn sich die Dateinamen bereits in einer Datei befinden, müssen Sie möglicherweise Folgendes verwenden
read
:read
kann sicher in Kombination mit verwendetfind
werden, indem das Trennzeichen entsprechend eingestellt wird:Für komplexere Suchvorgänge möchten Sie wahrscheinlich
find
entweder die-exec
Option oder Folgendes verwenden-print0 | xargs -0
:find
kann auch in das Verzeichnis jeder Datei kopiert werden, bevor ein Befehl mit-execdir
anstelle von ausgeführt wird-exec
, und kann interaktiv (Eingabeaufforderung vor Ausführung des Befehls für jede Datei) mit-ok
anstelle von-exec
(oder-okdir
anstelle von-execdir
) erstellt werden.*: Technisch gesehen führen beide
find
undxargs
(standardmäßig) den Befehl mit so vielen Argumenten aus, wie sie in die Befehlszeile passen, so oft, bis alle Dateien durchlaufen sind. In der Praxis spielt es keine Rolle, ob Sie über eine sehr große Anzahl von Dateien verfügen. Wenn Sie die Länge überschreiten, diese jedoch alle in derselben Befehlszeile benötigen, findenSie bei SOLeinen anderen Weg.quelle
done < filename
und die folgenden mit dem Rohr der stdin nicht mehr (→ nicht mehr interaktiven Sachen innerhalb der Schleife) verwendet werden kann, aber in Fällen , wo es gebraucht wird man verwenden kann ,3<
anstatt<
hinzuzufügen<&3
oder-u3
zu dasread
Teil, im Grunde mit einem separaten Dateideskriptor. Ich glaube auch, dassread -d ''
es dasselbe ist wie,read -d $'\0'
aber ich kann derzeit keine offizielle Dokumentation dazu finden.-exec process {} \;
und ich vermute, das ist eine ganz andere Frage - was bedeutet das und wie manipuliere ich es? Wo ist ein gutes Q / A oder Doc. drauf?man find
) schauen . In diesem Fall wird-exec
angewiesenfind
, den folgenden durch;
(oder+
) beendeten Befehl auszuführen, der{}
durch den Namen der zu verarbeitenden Datei ersetzt wird (oder, falls+
verwendet, durch alle Dateien, die diese Bedingung erfüllt haben).-d ''
ist besser als-d $'\0'
. Letzteres ist nicht nur länger, sondern legt auch nahe, dass Sie Argumente mit Null-Bytes übergeben könnten, dies jedoch nicht. Das erste Null-Byte markiert das Ende der Zeichenfolge. In bash$'a\0bc'
ist dasselbe wiea
und$'\0'
ist dasselbe wie$'\0abc'
oder nur die leere Zeichenfolge''
.help read
gibt an, dass " das erste Zeichen von delim verwendet wird, um die Eingabe zu beenden ", so dass die Verwendung''
als Trennzeichen ein kleiner Hack ist. Das erste Zeichen in der leeren Zeichenfolge ist das Nullbyte, das immer das Ende der Zeichenfolge markiert (auch wenn Sie es nicht explizit aufschreiben).Was auch immer Sie tun, verwenden Sie keine
for
Schleife :Drei Gründe:
find
muss sie vollständig ausgeführt werden.for
32 KB und Ihre Schleife gibt 40 KB Text zurück. Die letzten 8 KB werden direkt von Ihrerfor
Schleife entfernt und Sie werden es nie erfahren.Verwenden Sie immer ein
while read
Konstrukt:Die Schleife wird ausgeführt, während der
find
Befehl ausgeführt wird. Außerdem funktioniert dieser Befehl auch dann, wenn ein Dateiname mit Leerzeichen zurückgegeben wird. Und Sie werden Ihren Befehlszeilenpuffer nicht überlaufen lassen.Der
-print0
verwendet NULL als Dateitrennzeichen anstelle eines Zeilenumbruchs und-d $'\0'
verwendet beim Lesen NULL als Trennzeichen.quelle
-exec
stattdessen find's .-exec
ist am sichersten, da die Shell überhaupt nicht verwendet wird. NL in Dateinamen ist jedoch ziemlich selten. Leerzeichen in Dateinamen sind weit verbreitet. Der Hauptpunkt ist, keinefor
Schleife zu verwenden , die von vielen Postern empfohlen wird.for file $(find)
wegen der damit verbundenen Probleme nicht zu verwenden .-r
Option verwenden, umread
:-r raw input - disables interpretion of backslash escapes and line-continuation in the read data
Hinweis: Diese Methode und die (zweite) Methode von bmargulies können sicher mit Leerzeichen in den Datei- / Ordnernamen verwendet werden.
Um auch den - etwas exotischen - Fall von Zeilenumbrüchen in den behandelten Datei- / Ordnernamen zu haben, müssen Sie auf das
-exec
Prädikatfind
wie folgt zurückgreifen :Das
{}
ist der Platzhalter für das gefundene Objekt und\;
wird zum Beenden des verwendet-exec
Prädikats verwendet.Und der Vollständigkeit halber möchte ich noch eine Variante hinzufügen - Sie müssen die * nix-Methoden wegen ihrer Vielseitigkeit lieben:
Dies würde die gedruckten Elemente mit einem
\0
Zeichen trennen , das meines Wissens in keinem der Dateisysteme in Datei- oder Ordnernamen zulässig ist, und sollte daher alle Grundlagen abdecken.xargs
holt sie dann eins nach dem anderen ab ...quelle
find -print0
undxargs -0
beide GNU - Erweiterungen sind und nicht tragbar (POSIX) Argumente. Unglaublich nützlich auf den Systemen, auf denen sie vorhanden sind!read -r
würden), oder bei Dateinamen, die auf Leerzeichen enden (die behobenIFS= read
würden). Daher BashFAQ # 1 darauf hindeutetwhile IFS= read -r filename; do ...
exit
nicht der Fall , sodass beispielsweise nicht wie erwartet funktioniert und die im Hauptteil der Schleife festgelegten Variablen nach der Schleife nicht verfügbar sind.Dateinamen können Leerzeichen und sogar Steuerzeichen enthalten. Leerzeichen sind (Standard-) Trennzeichen für die Shell-Erweiterung in Bash und werden daher
x=$(find . -name "*.txt")
aus der Frage überhaupt nicht empfohlen. Wenn find einen Dateinamen mit Leerzeichen"the file.txt"
erhält, erhalten Sie z. B. 2 getrennte Zeichenfolgen zur Verarbeitung, wenn Siex
in einer Schleife verarbeiten. Sie können dies verbessern, indem Sie das Trennzeichen (Bash-IFS
Variable) ändern, z. B. in\r\n
, Dateinamen können jedoch Steuerzeichen enthalten. Dies ist also keine (vollständig) sichere Methode.Aus meiner Sicht gibt es zwei empfohlene (und sichere) Muster für die Verarbeitung von Dateien:
1. Verwenden Sie für die Erweiterung von Schleife und Dateinamen:
2. Verwenden Sie find-read-while & process substitution
Bemerkungen
zu Muster 1:
nullglob
kann verwendet werden, um diese zusätzliche Zeile zu vermeiden.failglob
Shell-Option festgelegt ist und keine Übereinstimmungen gefunden werden, wird eine Fehlermeldung gedruckt und der Befehl nicht ausgeführt." (aus dem obigen Bash-Handbuch)globstar
: "Wenn festgelegt, stimmt das in einem Dateinamenerweiterungskontext verwendete Muster '**' mit allen Dateien und null oder mehr Verzeichnissen und Unterverzeichnissen überein. Wenn dem Muster ein '/' folgt, stimmen nur Verzeichnisse und Unterverzeichnisse überein." sehen Bash Manual, Shopt Builtinextglob
,nocaseglob
,dotglob
& Shell - VariableGLOBIGNORE
zu Muster 2:
Dateinamen können Rohlingen, Tabulatoren, Leerzeichen, Zeilenumbrüche, ... zu verarbeiten Dateinamen in einer sicheren Art und Weise enthalten,
find
mit-print0
verwendet wird: Dateiname wird mit allen Steuerzeichen gedruckt und mit NUL beendet. Siehe auch Gnu Findutils Manpage, Unsichere Behandlung von Dateinamen , sichere Behandlung von Dateinamen , ungewöhnliche Zeichen in Dateinamen . Siehe David A. Wheeler unten für eine detaillierte Diskussion dieses Themas.Es gibt einige mögliche Muster, um Suchergebnisse in einer while-Schleife zu verarbeiten. Andere (Kevin, David W.) haben gezeigt, wie man das mit Rohren macht:
Wenn Sie diesen Code ausprobieren, werden Siefiles_found
feststellen , dass er nicht funktioniert: ist immer "wahr" und der Code gibt immer "keine Dateien gefunden" aus. Grund ist: Jeder Befehl einer Pipeline wird in einer separaten Subshell ausgeführt, sodass die geänderte Variable in der Schleife (separate Subshell) die Variable im Haupt-Shell-Skript nicht ändert. Aus diesem Grund empfehle ich die Verwendung der Prozesssubstitution als "besseres", nützlicheres und allgemeineres Muster.Siehe Ich setze Variablen in einer Schleife, die sich in einer Pipeline befindet. Warum verschwinden sie ... (aus Gregs Bash-FAQ) für eine ausführliche Diskussion zu diesem Thema.
Zusätzliche Referenzen und Quellen:
Gnu Bash Manual, Pattern Matching
Dateinamen und Pfadnamen in Shell: Wie es richtig gemacht wird, David A. Wheeler
Warum liest du keine Zeilen mit "für", Gregs Wiki?
Warum sollten Sie die Ausgabe von ls (1), Gregs Wiki, nicht analysieren?
Gnu Bash Manual, Prozessersetzung
quelle
(Aktualisiert, um @ Socowis hervorragende Geschwindigkeitsverbesserung einzuschließen)
Mit allen
$SHELL
, die es unterstützen (dash / zsh / bash ...):Getan.
Ursprüngliche Antwort (kürzer, aber langsamer):
quelle
\;
können Sie+
so viele Dateien wie möglich an eine einzelne übergebenexec
. Verwenden Sie dann"$@"
innerhalb des Shell-Skripts alle diese Parameter.$@
weggelassen wird, da es normalerweise der Name des Skripts ist. Wir müssen nur hinzufügendummy
dazwischen'
und{}
so den Ort des Skripts Namen nehmen kann, um sicherzustellen , alle Spiele von der Schleife verarbeitet werden.OTHERVAR=foo find . -na.....
sollte Ihnen den Zugriff$OTHERVAR
von dieser neu erstellten Shell aus ermöglichen.quelle
for x in $(find ...)
wird für jeden Dateinamen mit Leerzeichen unterbrochen. Gleiches gilt für, esfind ... | xargs
sei denn, Sie verwenden-print0
und-0
find . -name "*.txt -exec process_one {} ";"
stattdessen. Warum sollten wir xargs verwenden, um Ergebnisse zu sammeln, die wir bereits haben?process_one
ist. Wenn es sich um einen Platzhalter für einen tatsächlichen Befehl handelt , stellen Sie sicher, dass dies funktioniert (wenn Sie Tippfehler beheben und anschließend abschließende Anführungszeichen hinzufügen"*.txt
). Wennprocess_one
es sich jedoch um eine benutzerdefinierte Funktion handelt, funktioniert Ihr Code nicht.Sie können Ihre
find
Ausgabe im Array speichern, wenn Sie die Ausgabe später wie folgt verwenden möchten:Um nun jedes Element in einer neuen Zeile zu drucken, können Sie entweder eine
for
Schleifeniteration für alle Elemente des Arrays verwenden oder die printf-Anweisung verwenden.oder
Sie können auch verwenden:
Dadurch wird jeder Dateiname in Newline gedruckt
Um die
find
Ausgabe nur in Listenform zu drucken , können Sie eine der folgenden Methoden verwenden:oder
Dadurch werden Fehlermeldungen entfernt und nur der Dateiname als Ausgabe in einer neuen Zeile angegeben.
Wenn Sie etwas mit den Dateinamen tun möchten, ist es gut, sie im Array zu speichern. Andernfalls müssen Sie diesen Speicherplatz nicht belegen, und Sie können die Ausgabe direkt ausdrucken
find
.quelle
Wenn Sie davon ausgehen können, dass die Dateinamen keine Zeilenumbrüche enthalten, können Sie die Ausgabe
find
mit dem folgenden Befehl in ein Bash-Array einlesen :Hinweis:
-t
bewirktreadarray
, dass Zeilenumbrüche entfernt werden.readarray
es sich in einer Pipe befindet, daher die Prozessersetzung.readarray
ist seit Bash 4 verfügbar.Bash 4.4 und höher unterstützt auch den
-d
Parameter zur Angabe des Trennzeichens. Die Verwendung des Nullzeichens anstelle von Zeilenumbruch zur Begrenzung der Dateinamen funktioniert auch in dem seltenen Fall, dass die Dateinamen Zeilenumbrüche enthalten:readarray
kann auch wiemapfile
mit den gleichen Optionen aufgerufen werden .Referenz: https://mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream
quelle
exit
beimreadarray -d '' x < <(find . -name '*.txt' -print0)
Ich verwende gerne find, das zuerst der Variablen zugewiesen und IFS wie folgt auf eine neue Zeile umgeschaltet wird:
Nur für den Fall, dass Sie weitere Aktionen für denselben DATENSatz wiederholen möchten und feststellen möchten, dass die Suche auf Ihrem Server sehr langsam ist (hohe E / A-Auslastung).
quelle
Sie können die von zurückgegebenen Dateinamen
find
in ein Array wie folgt einfügen:Jetzt können Sie einfach das Array durchlaufen, um auf einzelne Elemente zuzugreifen und mit ihnen zu tun, was Sie wollen.
Hinweis: Es ist ein Leerraum sicher.
quelle
mapfile -t -d '' array < <(find ...)
. EinstellungIFS
ist nicht erforderlich fürmapfile
.basierend auf anderen Antworten und Kommentaren von @phk, mit fd # 3:
(was immer noch erlaubt, stdin innerhalb der Schleife zu verwenden)
quelle
find <path> -xdev -type f -name *.txt -exec ls -l {} \;
Dadurch werden die Dateien aufgelistet und Details zu Attributen angegeben.
quelle
Wie wäre es, wenn Sie grep anstelle von find verwenden?
Jetzt können Sie diese Datei lesen und die Dateinamen liegen in Form einer Liste vor.
quelle