Рекурсивно повторюйте файли в каталозі


15

Рекурсивно ітерацію файлів у каталозі легко зробити:

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

Однак вищесказане не працює для складніших речей, де потрібно зробити багато умовних гілок, петлі тощо. Раніше я використовував це для вищезазначеного:

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

Однак, схоже, це не працює для файлів, що містять незрозумілі символи:

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

Чи є альтернатива, яка добре поводиться з такими незрозумілими персонажами?


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, але частина оболонки вимагає удару. З * BSD і GNU знаходьте, ви можете використовувати -print0замість -exec printf '%s\0' {} +, це буде трохи швидше.)

Це дає можливість використовувати стандартний вхід у циклі, і він працює з будь-яким контуром.


1
Тому що мені довелося його переглянути: "читати ... Якщо ніяких імен не вводиться, рядок зчитування присвоюється змінній REPLY." Отожdo echo "Filename is '$REPLY'"
Андрій

9

Робити це так просто, як:

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

Або ...

find -exec executable_script {} \;

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:

Пам’ятайте, що до **/башти 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, це має вирішувати всі ваші дивні проблеми з іменем файлу.


1
-d ''еквівалентно -d $'\0'.
l0b0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.