ファイル名にスペースが含まれている場合に、findコマンドの出力を解析するにはどうすればよいですか?


12

などのループを使用する

for i in `find . -name \*.txt` 

一部のファイル名にスペースが含まれていると壊れます。

この問題を回避するためにどのようなテクニックを使用できますか?


1
ファイルのファイル名に改行を含めることもできます。そのため、とがfind -print0ありxargs -0ます。
ダニエルベック

回答:


12

理想的には、シェルスクリプトでファイル名を適切に解析することは常に難しいため、そのようにしないことが理想的です(スペースを修正すると、他の埋め込み文字、特に改行で問題が発生します)。これは、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などの言語で行うようになります。


1
Pythonを使用してこれをすべて回避するというアイデアが気に入っています。
スコットCウィルソン

12

使用find -print0してにパイプするxargs -0か、独自の小さなCプログラムを作成して、小さなCプログラムにパイプします。これが何で-print0あり-0、発明されたものです。

シェルスクリプトは、スペースを含むファイル名を処理する最良の方法ではありません。実行できますが、不格好になります。


私のマシンで動作します^ TM!
-mcandre

2

「内部フィールドセパレータ」(IFS)を、ループ引数の分割のためのスペース以外の何かに設定できます。例えば

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

IFSfindで使用した後、主に見栄えが良いためにリセットします。改行に設定しても問題はありませんが、これは「クリーナー」だと思います。

別の方法は、からの出力をどのように処理するかに応じて、コマンドでfind直接使用するか、使用してパイプで処理します。最初のケースでは、ファイル名のエスケープを処理します。この場合、その出力をヌル区切り文字で出力してから、これで分割します。ファイル名にその文字(私が知っていること)を含めることはできないため、これも常に安全です。これは、単純な場合に主に役立ちます。通常、完全なループの優れた代替物ではありません。-execfind-print0xargs -0find-print0findxargsfor


1

使用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
ノート

1

私は反対し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\; `)


1
両方の観点にある程度の妥当性があります。自分のファイルだけを扱っていたときは、ファイルの名前にスペース(またはキャリッジリターン!)が含まれていないため、findを使用するだけで心配する必要はありません。ただし、他の人のファイルで作業を開始するときは、より堅牢な手法を使用する必要があります。
スコットCウィルソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.