Рекурсивно перебирать файлы в каталоге

15

Рекурсивно перебирать файлы в каталоге можно легко:

find . -type f -exec bar {} \;

Тем не менее, вышеописанное не работает для более сложных вещей, где необходимо выполнить много условных переходов, циклов и т. Д. Я использовал это для вышеупомянутого:

while read line; do [...]; done < <(find . -type f)

Однако, похоже, что это не работает для файлов, содержащих непонятные символы:

$ touch $'a\nb'
$ find . -type f
./a?b

Есть ли альтернатива, которая хорошо справляется с такими непонятными персонажами?

user2064000
источник
1
find ... -exec bash -c 'echo filename is in \$0: "$0"' {} \;это лучший способ сделать это.
jw013
Вы можете решить эту проблему и сохранить свой оригинальный дизайн, изменив свой read lineна IFS= read -r line. Единственный символ, который сломает это тогда, является новой строкой.
Патрик
1
@Patrick, но имена файлов могут содержать символы новой строки. Вот почему -d $'\0'предпочтительнее.
Godlygeek

Ответы:

7

Еще одно использование для безопасности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' {} +, это будет немного быстрее.)

Это позволяет использовать стандартный ввод в цикле, и он работает с любым путем.

l0b0
источник
1
Потому что мне пришлось искать это: «читать ... Если имена не указаны, строка чтения присваивается переменной REPLY». Итакdo echo "Filename is '$REPLY'"
Андрей
9

Сделать это так же просто, как:

find -exec sh -c 'inline script "$0"' {} \;

Или...

find -exec executable_script {} \;
mikeserv
источник
5

Самый простой (но безопасный) подход заключается в использовании глобализации оболочки:

$ 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, это должно решить все ваши странные проблемы с именами файлов.

godlygeek
источник
1
-d ''эквивалентно -d $'\0'.
10