it-swarm-ja.com

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

次のようなループを使用する

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

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

この問題を回避するためにどのような手法を使用できますか?

12
Scott C Wilson

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

12
geekosaur

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

シェルスクリプトは、スペースを含むファイル名を処理するための最良の方法ではありません。それは可能ですが、扱いにくくなります。

12
D.W.

「内部フィールドセパレータ」(IFS)は、ループ引数分割用のスペース以外の値に設定できます。

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

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

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

2

find -print0xargs -0の併用

find -print0xargs -0と組み合わせて使用​​することは、正当なファイル名に対して完全に堅牢であり、利用可能な最も拡張可能な方法の1つです。たとえば、現在のディレクトリ内のすべての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よりも正確に引数をエコーするスクリプトを使用することにより、echoが入力に与える影響、特に-nの影響を明確に確認できます。

$ 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
jpaugh

bash bashersに同意しません。bashは、* nixツールセットと共に、ファイル(名前に空白が埋め込まれているものも含む)の処理に長けているためです。

実際、findを使用すると、処理するファイルをきめ細かく制御できます... bash側では、文字列をbash wordsにする必要があることを理解するだけで済みます。通常、「二重引用符」、またはIFSを使用するような他のメカニズムを使用するか、または{}

ほとんど/多くの状況では、IFSを設定およびリセットする必要がないことに注意してください。以下の例に示すように、ローカルでIFSを使用するだけです。 3つすべてが空白をうまく処理します。また、find's\;is事実上ループであるため、「標準」ループ構造は必要ありません。ループロジックを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//'  

'findalso allows you to pass multiple filenames as args to you script ..(if it suits your need: use + instead \; `)

1
Peter.O