現在のディレクトリとそのサブディレクトリにあるファイルのリストを取得したい(ワンライナースクリプトを使用したい):
IFS=$(echo -en "\n\b");
for FILE in $(find -type f); do echo "$FILE"; done
通常、それは期待どおりに機能しますが、最近、私のファイルリストで次のようになりました。
file_.doc
file_0.doc
file_ [2006_02_25] .doc
file_ [2016_06_16] .odt
file_ [2016_06_16] .pdf
file_ [16-6-2006] .doc
file_.pdf
file_ 4-4-2006.doc
出力は次のとおりです。
./file_.doc
./file_0.doc
./file_0.doc
./file_[2016_06_16].odt
./file_[2016_06_16].pdf
./file_0.doc
./file_.pdf
./file_ 4-4-2006.doc
変数IFSを次のように変更した場合:
IFS=$(echo -en "\n");
次に、出力は(修正されます):
./file_.doc
./file_0.doc
./file_[2006_02_25].doc
./file_[2016_06_16].odt
./file_[2016_06_16].pdf
./file_[16-6-2006].doc
./file_.pdf
./file_ 4-4-2006.doc
'\b'
が neccesary であることを読み、 printf
の代わりにecho
を使用 という解決策を見つけました。
私の質問は次のとおりです。
1)これらの出力が異なる理由を説明してください。
2)上記のprintf
を使用したソリューションは、echo -en "\n\b"
の代わりになる可能性がありますか?
コマンド置換の出力は、Word分割(IFS
を設定することで処理しました)、およびファイル名グロブの対象になります。 [abc]
構文は、通常どおり「任意の文字a
、b
、c
に一致」であり、[2006_02_25].doc
は0.doc
に一致します。
Bash/ksh/zshでは、ダブルスターを使用して、ディレクトリツリー内のすべてのファイルを取得できます(現在のディレクトリだけでなく、再帰的に)。これにより、例と同じファイルが見つかります。
shopt -s globstar # in Bash
# set -o globstar # in ksh
for file in **/* ; do
[[ -f $file ]] || continue # check it's a regular file, like find -type f
...
done
もちろん、find
is強力なので、条件がたくさんある場合は、それを使用する方が簡単な場合があります。その場合は、IFS
の修正に加えて、set -f
でファイル名のグロブを無効にする必要があります。
set -f
IFS=$'\n'
for file in $(find -type f -some -other -conditions) ; do
...
done
または、代わりにプロセス置換を使用してwhile read
ループを使用します。
while IFS= read -r file ; do
...
done < <(find -type f -some -other -conditions)
(上記はfind ... | while ...
に似ていますが、パイプラインの最後の部分がサブシェルで実行されるという問題を回避します。)
これらは両方とも、find
の出力の区切り文字として使用されるため、ファイル名に改行が含まれていないことを前提としています。 (そして$(..)
が最後の改行を食べるからです。)
少なくともBashには、ファイル名の改行を機能させる方法が実際にあります。 read
区切り文字を空の文字列に設定すると、NULバイトを区切り文字として効果的に使用できます。そう:
while IFS= read -d '' -r file ; do
...
done < <(find -type f -some -other -conditions -print0)
それは書くのが面倒になり始めていますが、入力を操作せずに機能させるには、read
に対するこれらすべてのオプションが必要です。
IFS
...については、IFS=$(echo -en "\n");
を設定すると、IFS
が空の文字列に設定され(コマンド置換によって末尾の改行が消費されるため)、結果としてno =分割。この場合の出力は、行ごとではなく、一度にfind
のすべての出力を取得するため、正しいように見えます。これにより、ファイル名のグロブに関する問題も隠されます。これは、完全な複数行の文字列がどのファイル名とも一致せず、そのまま渡されるためです。
ループ値を出力する以外のことを行うと、違いがわかります。いくつかのセパレーターを追加してみてください。
IFS=$(echo -en "\n") # same as IFS=
for FILE in $(find -type f); do echo "<$FILE>"; done
最も単純な標準シェル以外でIFS
を改行に設定する最も簡単な方法は、IFS=$'\n'
です。
$( find ... )
を実行しないでください。ファイル名の生成(グロブ)が呼び出され、一部のファイル名は他のファイル名と一致するグロブパターンとして解釈されます。たとえば、パターンfile_[2006_02_25].doc
とfile_[16-6-2006].doc
はfile_0.doc
と一致します。そのため、これら2つのパターンの代わりにこのファイル名が使用されます。
さらに、コマンド置換のfind
コマンドがすべてのパス名を生成するまで、ループは反復を開始しません。これは、一般的な場合、かなりの量のメモリを消費する可能性があり、実際にはそうではありませんエレガント。
代わりに、find
を使用してください(IFS
は変更しないでください)。
find . type -f -print
これらのファイルで他のことをしたい場合は、-exec
で行うことができます。
find . -type f -exec sh -c 'printf "Found the file %s\n" "$@"' sh {} +
現在のディレクトリ内のファイルを処理したいだけの場合は、単に
for name in *; do
printf 'Found the name %s\n' "$name"
done
関連: