Existuje také velmi jednoduché řešení:spolehnout se na 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'
Všimněte si, že si nejsem jistý, zda je toto chování výchozí, ale v mém obchodě nevidím žádné speciální nastavení, takže bych řekl, že by to mělo být "bezpečné" (testováno na osx a ubuntu).
Existuje několik funkčních způsobů, jak toho dosáhnout.
Pokud se chcete držet své původní verze, můžete to udělat takto:
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
To stále selže, pokud názvy souborů obsahují doslovné nové řádky, ale mezery to neporuší.
Není však nutné zasahovat do IFS. Zde je můj preferovaný způsob, jak to udělat:
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
Pokud najdete < <(command)
syntaxe neznámá, měli byste si přečíst o substituci procesů. Výhoda oproti for file in $(find ...)
je, že soubory s mezerami, novými řádky a dalšími znaky jsou správně zpracovány. To funguje, protože find
s -print0
použije null
(také znám jako \0
) jako terminátor pro každý název souboru a na rozdíl od nového řádku není null v názvu souboru povoleným znakem.
Výhoda oproti téměř ekvivalentní verzi
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
Je to, že jakékoli přiřazení proměnných v těle cyklu while je zachováno. To znamená, že pokud přejdete na while
jako výše, pak tělo while
je v podskořápce, která nemusí být to, co chcete.
Výhoda verze procesní substituce oproti find ... -print0 | xargs -0
je minimální:xargs
verze je v pořádku, pokud vše, co potřebujete, je vytisknout řádek nebo provést jednu operaci se souborem, ale pokud potřebujete provést více kroků, je verze se smyčkou jednodušší.
UPRAVIT :Zde je pěkný testovací skript, abyste si mohli udělat představu o rozdílu mezi různými pokusy o vyřešení tohoto problému
#!/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"
Iteraci založenou na slovech můžete nahradit iterací založenou na řádcích:
find . -iname "foo*" | while read f
do
# ... loop body
done