C'è anche una soluzione molto semplice:affidarsi a 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'
Nota che non sono sicuro che questo comportamento sia quello predefinito, ma non vedo alcuna impostazione speciale nel mio negozio, quindi direi che dovrebbe essere "sicuro" (testato su osx e ubuntu).
Esistono diversi modi praticabili per ottenere questo risultato.
Se vuoi restare fedele alla tua versione originale, puoi farlo in questo modo:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
Ciò fallirà comunque se i nomi dei file contengono letterali newline, ma gli spazi non lo interromperanno.
Tuttavia, non è necessario scherzare con IFS. Ecco il mio modo preferito per farlo:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Se trovi il < <(command)
sintassi sconosciuta dovresti leggere sulla sostituzione dei processi. Il vantaggio di questo rispetto a for file in $(find ...)
è che i file con spazi, newline e altri caratteri vengono gestiti correttamente. Funziona perché find
con -print0
userà un null
(ovvero \0
) come terminatore per ogni nome di file e, a differenza della nuova riga, null non è un carattere legale in un nome di file.
Il vantaggio di questo rispetto alla versione quasi equivalente
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
È che qualsiasi assegnazione variabile nel corpo del ciclo while viene preservata. Cioè, se esegui il pipe a while
come sopra quindi il corpo del while
è in una subshell che potrebbe non essere ciò che desideri.
Il vantaggio della versione di sostituzione del processo rispetto a find ... -print0 | xargs -0
è minimo:il xargs
version va bene se tutto ciò di cui hai bisogno è stampare una riga o eseguire una singola operazione sul file, ma se devi eseguire più passaggi la versione loop è più semplice.
MODIFICA :Ecco un simpatico script di test in modo che tu possa avere un'idea della differenza tra i diversi tentativi di risolvere questo problema
#!/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"
Potresti sostituire l'iterazione basata sulla parola con una basata sulla riga:
find . -iname "foo*" | while read f
do
# ... loop body
done