TL;DR
while read -d $'\0' file; do
"${file}" に対して処理
done < <(find ディレクトリ -mindepth 1 -maxdepth 1 -print0)
よく見るやり方
シェルスクリプトでディレクトリ内のすべてのファイルをループする方法をぐぐると、次の二つの方法が出てくるのですが、どちらも意図通りに動かない場合があります。
-
ワイルドカードをfor
for file in ディレクトリ/*; do
という書き方。
ディレクトリが空だったときにうまく動きません。zshだと"no matches found: *"的なエラーとなりますし、bashだと"*"という値をfile
に渡してきます。
先にディレクトリが空かどうか判定すればいいのではないか、まあそれで99.9%くらいOKなんですが、レースコンディションが気になって夜も眠れなくなる人はもう少し読み進めていただければ。 -
lsもしくはfindでfor
for file in $(ls ディレクトリ); do
もしくは
for file in $(find ディレクトリ -name * -depth 1); do
という書き方。
空ディレクトリ問題があっさり解決します。
しかし、ファイル名がホワイトスペースや引用符文字を含んでいる場合に誤ったファイル名を受け取ってしまうことになります。
行ごとにループを回す発想
後者のlsまたはfindの結果を使う方法、ホワイトスペース区切りでループしようとしたので問題となっていました。
しかしlsの出力は一行に一エントリ結果が入っているのだから、行ごとにループするというシェルスクリプトの普通のやり方に持ち込めばそういう問題になりません。
行ごとにループするには、while read line
でしたね。
そこでまずこう書いてみます。
while read file; do
"ディレクトリ/${file}" に対して処理
done < <(ls -A -1 ディレクトリ)
lsの-A
は.
, ..
以外のすべてのファイルを表示せよ、-1
は1行に1ファイルで区切れというオプションです。
パイプではなく無理やりリダイレクションの形にしてあるのは、パイプで書いてしまうとループ内で書き込んだ変数がループ外へ持ち出せなくなるためです。
うまく動きましたか?
しかしまだ問題が。
改行文字を含むファイル名が2行に切られます。
行区切りをnull文字にする
改行コードはファイル名に含まれうるのが問題でした。
それならファイル名に使えない文字を行区切りにしましょう。null文字です。
findコマンドは-print0
オプションを使うことでnull文字を区切りに出力してくれます。
readコマンドは-d
オプションで区切り文字を指定できます。
そこで最初に書いたこのコードです。
while read -d $'\0' file; do
"${file}" に対して処理
done < <(find ディレクトリ -mindepth 1 -maxdepth 1 -print0)