などのループを使用する
for i in `find . -name \*.txt`
一部のファイル名にスペースが含まれていると壊れます。
この問題を回避するためにどのようなテクニックを使用できますか?
などのループを使用する
for i in `find . -name \*.txt`
一部のファイル名にスペースが含まれていると壊れます。
この問題を回避するためにどのようなテクニックを使用できますか?
回答:
理想的には、シェルスクリプトでファイル名を適切に解析することは常に難しいため、そのようにしないことが理想的です(スペースを修正すると、他の埋め込み文字、特に改行で問題が発生します)。これは、BashPitfallsページの最初のエントリーとしてリストされています。
そうは言っても、あなたが望むことをほぼ行う方法があります:
oIFS=$IFS
IFS=$'\n'
find . -name '*.txt' | while read -r i; do
# use "$i" with whatever you're doing
done
IFS=$oIFS
$i
後でスペースを解釈する他のことを避けるために、使用するときにも引用することを忘れないでください。また$IFS
、使用しない場合は後から混乱を招くエラーが発生するため、使用後は設定を忘れないでください。
これには、もう1つ注意事項がありwhile
ます。使用している正確なシェルによっては、ループ内で発生することがサブシェルで発生する可能性があるため、変数の設定が保持されない場合があります。for
ループのバージョン回避するが、あなたが適用された場合でも、その価格で$IFS
スペースを避けるの問題に対する解決策をすれば、あなたがトラブルに巻き込まれるだろうfind
戻ってあまりにも多くのファイル。
ある時点で、このすべての正しい修正は、シェルの代わりにPerlやPythonなどの言語で行うようになります。
「内部フィールドセパレータ」(IFS
)を、ループ引数の分割のためのスペース以外の何かに設定できます。例えば
ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
IFS=${ORIGIFS}
#do stuff
done
IFS=${ORIGIFS}
IFS
findで使用した後、主に見栄えが良いためにリセットします。改行に設定しても問題はありませんが、これは「クリーナー」だと思います。
別の方法は、からの出力をどのように処理するかに応じて、コマンドでfind
直接使用するか、使用してパイプで処理します。最初のケースでは、ファイル名のエスケープを処理します。この場合、その出力をヌル区切り文字で出力してから、これで分割します。ファイル名にその文字(私が知っていること)を含めることはできないため、これも常に安全です。これは、単純な場合に主に役立ちます。通常、完全なループの優れた代替物ではありません。-exec
find
-print0
xargs -0
find
-print0
find
xargs
for
find -print0
してxargs -0
使用find -print0
と組み合わせることはxargs -0
法律上のファイル名に対して完全に堅牢であり、そして利用できる最も拡張可能な方法の一つです。たとえば、現在のディレクトリ内のすべてのPDFファイルのリストが必要だとします。あなたは書くことができます
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo
これ-iname '*.pdf'
により、現在のディレクトリ(.
)および任意のサブディレクトリで(を介して)すべてのPDFが検索され、それぞれが引数としてecho
コマンドに渡されます。-n 1
オプションを指定したためxargs
、一度に1つの引数のみをに渡しますecho
。そのオプションを省略した場合、xargs
可能な限り多くをに渡すことになりecho
ます。(echo short input | xargs --show-limits
コマンドラインで許可されているバイト数を確認できます。)
xargs
正確には何をしますか?引数をより正確な方法でエコーするスクリプトを使用することによりxargs
、入力-n
、特にその効果に対する効果を明確に見ることができecho
ます。
$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"
[[ $# -eq 0 ]] && exit
for i in $(seq 1 $#); do
echo "Arg $i: <$1>"
shift
done
EOF
$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh
スペースと改行を完全に適切に処理することに注意してください。
$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh
次の一般的なソリューションでは特に面倒です。
chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
./echoArgs.sh "$file"
done
ノート
私は反対しbash
ているため、bashers bash
* nixのツールセットと一緒に、(名前に空白が埋め込まれているものを含む)のファイルを扱うには非常に熟達しています、。
実際にfind
は、処理するファイルの選択をきめ細かく制御できます... bash側では、文字列を作成する必要があることを認識するだけで十分bash words
です。通常、「二重引用符」を使用するか、IFSを使用するなどの他のメカニズムを使用するか、{}
ほとんど/多くの状況では、IFSを設定およびリセットする必要はありません。次の例に示すように、IFSをローカルで使用するだけです。3つすべてが空白をうまく処理します。また、find \;
は事実上ループなので、「標準」ループ構造は必要ありません。ループロジックをbash関数に配置するだけです(標準ツールを呼び出していない場合)。
IFS=$'\n' find ~/ -name '*.txt' -exec function-or-util {} \;
そして、さらに2つの例
IFS=$'\n' find ~/ -name '*.txt' -exec printf 'Hello %s\n' {} \;
IFS=$'\n' find ~/ -name '*.txt' -exec echo {} \+ |sed 's/home//'
'検索also allows you to pass multiple filenames as args to you script ..(if it suits your need: use
+ instead
\; `)
find -print0
ありxargs -0
ます。