Verwenden einer Schleife wie
for i in `find . -name \*.txt`
wird unterbrochen, wenn einige Dateinamen Leerzeichen enthalten.
Mit welcher Technik kann ich dieses Problem vermeiden?
Verwenden einer Schleife wie
for i in `find . -name \*.txt`
wird unterbrochen, wenn einige Dateinamen Leerzeichen enthalten.
Mit welcher Technik kann ich dieses Problem vermeiden?
Antworten:
Im Idealfall machen Sie das überhaupt nicht so, da es immer schwierig ist, Dateinamen in einem Shell-Skript richtig zu analysieren (korrigieren Sie es für Leerzeichen, Sie haben immer noch Probleme mit anderen eingebetteten Zeichen, insbesondere Zeilenumbrüchen). Dies ist sogar als erster Eintrag auf der BashPitfalls-Seite aufgeführt.
Es gibt jedoch eine Möglichkeit, fast das zu tun, was Sie wollen:
oIFS=$IFS
IFS=$'\n'
find . -name '*.txt' | while read -r i; do
# use "$i" with whatever you're doing
done
IFS=$oIFS
Denken Sie daran, auch $i
bei der Verwendung zu zitieren , um zu vermeiden, dass andere Dinge die Leerzeichen später interpretieren. Denken Sie auch daran, $IFS
nach der Verwendung zurückzusetzen, da dies später zu verwirrenden Fehlern führen kann.
Dies hat noch eine weitere Einschränkung: Was innerhalb der while
Schleife passiert , kann in einer Subshell stattfinden, abhängig von der genauen Shell, die Sie verwenden, sodass die variablen Einstellungen möglicherweise nicht bestehen bleiben. Die for
Loop-Version vermeidet dies, aber zu dem Preis, dass $IFS
Sie selbst dann Probleme bekommen, wenn Sie find
zu viele Dateien zurückgeben , selbst wenn Sie die Lösung anwenden , um Probleme mit Leerzeichen zu vermeiden .
Irgendwann wird die richtige Lösung für all dies in einer Sprache wie Perl oder Python anstelle von Shell ausgeführt.
Verwenden find -print0
und leiten Sie xargs -0
es an Ihr kleines C-Programm weiter oder schreiben Sie es an Ihr kleines C-Programm. Dafür wurden -print0
und -0
wurden erfunden.
Shell-Skripte sind nicht der beste Weg, um Dateinamen mit Leerzeichen zu behandeln: Sie können dies tun, aber es wird klobig.
Sie können das "interne Feldtrennzeichen" ( IFS
) auf etwas anderes als Platz für die Aufteilung des Schleifenarguments setzen, z
ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
IFS=${ORIGIFS}
#do stuff
done
IFS=${ORIGIFS}
Ich habe IFS
nach der Verwendung in find zurückgesetzt, hauptsächlich, weil es gut aussieht, denke ich. Ich habe keine Probleme damit gesehen, es auf Newline zu setzen, aber ich denke, das ist "sauberer".
Eine andere Methode, je nachdem, was Sie mit der Ausgabe von tun möchten find
, besteht darin, entweder direkt -exec
mit dem find
Befehl zu verwenden oder ihn zu verwenden -print0
und in ihn weiterzuleiten xargs -0
. Im ersten Fall find
wird dafür gesorgt, dass der Dateiname nicht mehr angezeigt wird. In diesem -print0
Fall wird find
die Ausgabe mit einem Nulltrennzeichen gedruckt und anschließend xargs
aufgeteilt. Da kein Dateiname dieses Zeichen enthalten kann (was ich weiß), ist dies auch immer sicher. Dies ist meistens in einfachen Fällen nützlich; und ist normalerweise kein guter Ersatz für eine vollständige for
Schleife.
find -print0
mitxargs -0
Die Verwendung in find -print0
Kombination mit xargs -0
ist absolut robust gegenüber legalen Dateinamen und eine der erweiterbarsten verfügbaren Methoden. Angenommen, Sie möchten eine Liste aller PDF-Dateien im aktuellen Verzeichnis. Du könntest schreiben
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo
Dadurch wird jedes PDF (via -iname '*.pdf'
) im aktuellen Verzeichnis ( .
) und in jedem Unterverzeichnis gefunden und jedes davon als Argument an den echo
Befehl übergeben. Da wir die -n 1
Option angegeben haben, xargs
wird jeweils nur ein Argument an übergeben echo
. Hätten wir diese Option weggelassen, xargs
wären so viele wie möglich an sie vorbeigekommen echo
. (Sie können echo short input | xargs --show-limits
sehen, wie viele Bytes in einer Befehlszeile zulässig sind.)
xargs
genau?Wir können deutlich sehen, welche Auswirkungen dies xargs
auf die Eingabe hat - und insbesondere auf die Auswirkungen -n
-, indem wir ein Skript verwenden, das seine Argumente genauer wiedergibt als echo
.
$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"
[[ $# -eq 0 ]] && exit
for i in $(seq 1 $#); do
echo "Arg $i: <$1>"
shift
done
EOF
$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh
Beachten Sie, dass Leerzeichen und Zeilenumbrüche perfekt verarbeitet werden.
$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh
Dies wäre besonders problematisch bei der folgenden allgemeinen Lösung:
chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
./echoArgs.sh "$file"
done
Anmerkungen
Ich bin mit den bash
Bashern nicht einverstanden, da sie bash
zusammen mit dem * nix-Tool-Set sehr gut mit Dateien umgehen können (einschließlich solcher, deren Namen Leerzeichen enthalten).
Tatsächlich haben find
Sie eine genaue Kontrolle über die Auswahl der zu verarbeitenden Dateien ... Auf der Bash-Seite müssen Sie wirklich nur erkennen, dass Sie Zeichenfolgen erstellen müssen bash words
. in der Regel durch Verwendung von "doppelten Anführungszeichen" oder eines anderen Mechanismus wie der Verwendung von IFS oder Finds{}
Beachten Sie, dass Sie in den meisten / vielen Situationen IFS nicht einstellen und zurücksetzen müssen. Verwenden Sie IFS einfach lokal, wie in den folgenden Beispielen gezeigt. Alle drei behandeln Leerzeichen in Ordnung. Auch brauchen Sie nicht eine „Standard“ Loop - Struktur, weil FIND \;
ist effektiv eine Schleife; Fügen Sie einfach Ihre Schleifenlogik in eine Bash-Funktion ein (wenn Sie kein Standardwerkzeug aufrufen).
IFS=$'\n' find ~/ -name '*.txt' -exec function-or-util {} \;
Und noch zwei Beispiele
IFS=$'\n' find ~/ -name '*.txt' -exec printf 'Hello %s\n' {} \;
IFS=$'\n' find ~/ -name '*.txt' -exec echo {} \+ |sed 's/home//'
'find also allows you to pass multiple filenames as args to you script ..(if it suits your need: use
+ instead
\; `)
find -print0
undxargs -0
.