web-dev-qa-db-ja.com

ディレクトリ内のファイルを再帰的に反復します

ディレクトリ内のファイルを再帰的に反復することは、次の方法で簡単に実行できます。

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

ただし、上記は、条件付き分岐やループなどの多くを実行する必要がある、より複雑なものには機能しません。私はこれを上記に使用していました:

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

ただし、これは不明瞭な文字を含むファイルでは機能しないようです。

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

そのようなあいまいな文字をうまく処理する代替手段はありますか?

15
user2064000

safe 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' {} +、少し速くなります。)

これにより、ループ内で標準入力を使用できるようになり、anyパスで機能します。

7
l0b0

これは次のように簡単です。

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

または...

find -exec executable_script {} \;
9
mikeserv

最も簡単な(しかし安全な)アプローチは、シェルグロビングを使用することです。

$ 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 -print0xargs -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は実際には正しいことに注意してください。

5
terdon

読み取りループを移植可能にするのは少し難しいですが、特にbashの場合は this のようなものを試すことができます。

関連部分:

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のファイル名またはパスでは発生しないバイトであるため、これはすべての奇妙なファイル名の問題を処理するはずです。

4
godlygeek