ループを持つ変数IFSとリストファイルの結果は異なります。

ループを持つ変数IFSとリストファイルの結果は異なります。

現在のディレクトリとそのサブディレクトリにあるファイルのリストを取得したいです(1行スクリプトを使用したい)。

IFS=$(echo -en "\n\b");
for FILE in $(find -type f); do echo "$FILE"; done

通常は期待どおりに動作しますが、最近私のファイルのリストは次のとおりです。

file_.doc
ファイル_0.doc
ファイル_[2006_02_25].doc
ファイル_[2016_06_16].odt
ファイル_[2016_06_16].pdf
ファイル_[16-6-2006].doc
ファイル_.pdf
ファイル_ 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'だことは必要、そして解決策を見つけました使用するprintf, 交換するecho

私の質問は次のとおりです

1)これらの出力がどのように異なるかを説明できますか?

2)上記の解決策を使用するprintf代替案はありますかecho -en "\n\b"

答え1

コマンド置換の出力は単語分割の影響を受けます(設定によってこれを処理できますIFS)。そしてファイル名ワイルドカード。設定[abc]は通常どおり「すべての文字a、、bcと一致し、[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 はい強力なので、条件が多い場合は使いやすくなります。これにより、set -f問題を解決するだけでなく、ファイル名のグロービングも無効にする必要がありますIFS

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いいえ分けた。この場合、find1行ずつインポートするのではなく、一度にすべてインポートするため、出力は正しいようです。複数行の文字列全体がファイル名と一致せずにそのまま渡されるため、ファイル名のグロービングの問題も隠されています。

ループ値を印刷する以外に他の操作を実行すると、違いを確認できます。いくつかの区切り文字を追加してみてください。

IFS=$(echo -en "\n")       # same as IFS=
for FILE in $(find -type f); do echo "<$FILE>"; done

IFS最も単純な標準シェル以外のシェルで改行文字を設定する最も簡単な方法はですIFS=$'\n'

答え2

これをしないでください$( find ... )。ファイル名の生成(ワイルドカード指定)を呼び出し、一部のファイル名は他のファイル名と一致するワイルドカードパターンとして解釈されます。たとえば、Patternfile_[2006_02_25].docfile_[16-6-2006].docmatchが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

関連:

関連情報