
Рекурсивный перебор файлов в каталоге можно легко выполнить следующим образом:
find . -type f -exec bar {} \;
Однако вышеприведенное не работает для более сложных вещей, где нужно делать много условных ветвлений, циклов и т. д. Я использовал это для вышеприведенного:
while read line; do [...]; done < <(find . -type f)
Однако, похоже, это не работает для файлов, содержащих непонятные символы:
$ touch $'a\nb'
$ find . -type f
./a?b
Есть ли альтернатива, которая хорошо справляется с такими малоизвестными персонажами?
решение1
Еще одно применение длябезопасныйfind
:
while IFS= read -r -d '' -u 9
do
[Do something with "$REPLY"]
done 9< <( find . -type f -exec printf '%s\0' {} + )
(Это работает с любым POSIX find
, но для оболочки требуется bash. С *BSD и GNU find вы можете использовать -print0
вместо -exec printf '%s\0' {} +
, это будет немного быстрее.)
Это позволяет использовать стандартный ввод внутри цикла и работает слюбойпуть.
решение2
Сделать это очень просто:
find -exec sh -c 'inline script "$0"' {} \;
Или...
find -exec executable_script {} \;
решение3
Самый простой (но безопасный) подход — использовать подстановку оболочки:
$ for f in *; do printf ":%s:\n" "$f"; done
:a b:
:c
d:
:-e:
:e f:
h:
Чтобы выполнить рекурсию выше в подкаталоги (в bash), можно использовать опцию globstar
; также установите dotglob
для сопоставления файлов, имена которых начинаются с .
:
$ shopt -s globstar dotglob
$ for f in **/*; do printf ":%s:\n" "$f"; done
:a b:
:c
d:
:-e:
:e f:
:foo:
:foo/file1:
:foo/file two:
h:
Обратите внимание, что до bash 4.2 **/
рекурсия в символические ссылки на каталоги. Начиная с bash 4.3 **/
рекурсия только в каталоги, например find
.
Другим распространенным решением является find -print0
использование xargs -0
:
$ touch -- 'a b' $'c\nd' $'e\tf' $'g\rh' '-e'
$ find . -type f -print0 | xargs -0 -I{} printf ":%s:\n" {}
h:/g
:./e f:
:./a b:
:./-e:
:./c
d:
Обратите внимание, что h:/g
на самом деле это правильно, поскольку имя файла содержит расширение \r
.
решение4
Немного сложно сделать цикл чтения переносимым, но для bash в частности вы можете попробовать что-то вродеэтот.
Соответствующая часть:
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
Это предписывает find
печатать вывод, разделенный символами NUL (0x00), и read
извлекать строки, разделенные символами NUL ( -d $'\0'
), не обрабатывая обратные косые черты как экранирования других символов ( -r
) и не выполняя никаких разбиений слов в строках ( IFS=
). Поскольку 0x00 — это байт, который не может встречаться в именах файлов или путях в Unix, это должно решить все ваши странные проблемы с именами файлов.