Es gibt auch eine sehr einfache Lösung:Verlassen Sie sich auf Bash Globbing
$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid file 3"
$ ls
stupid file 3 stupid file1 stupid file2
$ for file in *; do echo "file: '${file}'"; done
file: 'stupid file 3'
file: 'stupid file1'
file: 'stupid file2'
Beachten Sie, dass ich nicht sicher bin, ob dieses Verhalten das Standardverhalten ist, aber ich sehe keine spezielle Einstellung in meinem Shopt, also würde ich sagen, dass es "sicher" sein sollte (getestet auf OSX und Ubuntu).
Es gibt mehrere praktikable Möglichkeiten, dies zu erreichen.
Wenn Sie eng an Ihrer Originalversion festhalten möchten, können Sie dies folgendermaßen tun:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
Dies wird immer noch fehlschlagen, wenn Dateinamen wörtliche Zeilenumbrüche enthalten, aber Leerzeichen werden es nicht unterbrechen.
Es ist jedoch nicht erforderlich, mit IFS herumzuspielen. Hier ist meine bevorzugte Vorgehensweise:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Wenn Sie den < <(command)
finden Syntax ungewohnt sollten Sie sich über die Prozesssubstitution informieren. Der Vorteil gegenüber for file in $(find ...)
ist, dass Dateien mit Leerzeichen, Zeilenumbrüchen und anderen Zeichen korrekt behandelt werden. Das funktioniert, weil find
mit -print0
verwendet einen null
(auch bekannt als \0
) als Abschlusszeichen für jeden Dateinamen und im Gegensatz zu Newline ist null kein gültiges Zeichen in einem Dateinamen.
Der Vorteil gegenüber der nahezu gleichwertigen Version
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
Ist das jede Variablenzuweisung im Körper der While-Schleife erhalten? Das heißt, wenn Sie zu while
leiten wie oben dann der Körper der while
befindet sich in einer Subshell, die möglicherweise nicht das ist, was Sie wollen.
Der Vorteil der Prozesssubstitutionsversion gegenüber find ... -print0 | xargs -0
ist minimal:Die xargs
Version ist in Ordnung, wenn Sie nur eine Zeile drucken oder eine einzelne Operation an der Datei ausführen müssen, aber wenn Sie mehrere Schritte ausführen müssen, ist die Schleifenversion einfacher.
BEARBEITEN :Hier ist ein nettes Testskript, damit Sie sich ein Bild von den Unterschieden zwischen verschiedenen Versuchen zur Lösung dieses Problems machen können
#!/usr/bin/env bash
dir=/tmp/getlist.test/
mkdir -p "$dir"
cd "$dir"
touch 'file not starting foo' foo foobar barfoo 'foo with spaces'\
'foo with'$'\n'newline 'foo with trailing whitespace '
# while with process substitution, null terminated, empty IFS
getlist0() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# while with process substitution, null terminated, default IFS
getlist1() {
while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# pipe to while, newline terminated
getlist2() {
find . -iname 'foo*' | while read -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# pipe to while, null terminated
getlist3() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, default IFS
getlist4() {
for file in "$(find . -iname 'foo*')" ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, newline IFS
getlist5() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# see how they run
for n in {0..5} ; do
printf '\n\ngetlist%d:\n' $n
eval getlist$n
done
rm -rf "$dir"
Sie könnten die wortbasierte Iteration durch eine zeilenbasierte ersetzen:
find . -iname "foo*" | while read f
do
# ... loop body
done