現在のディレクトリとそのサブディレクトリにあるファイルのリストを取得したいです(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
、、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
はい強力なので、条件が多い場合は使いやすくなります。これにより、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
いいえ分けた。この場合、find
1行ずつインポートするのではなく、一度にすべてインポートするため、出力は正しいようです。複数行の文字列全体がファイル名と一致せずにそのまま渡されるため、ファイル名のグロービングの問題も隠されています。
ループ値を印刷する以外に他の操作を実行すると、違いを確認できます。いくつかの区切り文字を追加してみてください。
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].doc
とfile_[16-6-2006].doc
matchが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
関連: