forループのスペースを含むファイル名、findコマンド


34

複数のサブフォルダー内のすべてのファイルを検索してtarにアーカイブするスクリプトがあります。私のスクリプトは

for FILE in `find . -type f  -name '*.*'`
  do
if [[ ! -f archive.tar ]]; then

  tar -cpf archive.tar $FILE
else 
  tar -upf archive.tar $FILE 
fi
done

findコマンドを使用すると、次の出力が得られます

find . -type f  -iname '*.*'
./F1/F1-2013-03-19 160413.csv
./F1/F1-2013-03-19 164411.csv
./F1-FAILED/F2/F1-2013-03-19 154412.csv
./F1-FAILED/F3/F1-2011-10-02 212910.csv
./F1-ARCHIVE/F1-2012-06-30 004408.csv
./F1-ARCHIVE/F1-2012-05-08 190408.csv

ただし、FILE変数はパス./F1/F1-2013-03-19の最初の部分と、次の部分160413.csvのみを保存します。

readwhileループで使用してみましたが、

while read `find . -type f  -iname '*.*'`;   do ls $REPLY; done

しかし、次のエラーが表示されます

bash: read: `./F1/F1-2013-03-19': not a valid identifier

誰かが別の方法を提案できますか?

更新

以下の回答で示唆されているように、スクリプトを更新しました

#!/bin/bash

INPUT_DIR=/usr/local/F1
cd $INPUT_DIR
for FILE in "$(find  . -type f -iname '*.*')"
do
archive=archive.tar

        if [ -f $archive ]; then
        tar uvf $archive "$FILE"
        else
        tar -cvf $archive "$FILE"
        fi
done

私が得る出力は

./test.sh
tar: ./F1/F1-2013-03-19 160413.csv\n./F1/F1-2013-03-19 164411.csv\n./F1/F1-2013-03-19 153413.csv\n./F1/F1-2013-03-19 154412.csv\n./F1/F1-2012-09-10 113409.csv\n./F1/F1-2013-03-19 152411.csv\n./.tar\n./F1-FAILED/F3/F1-2013-03-19 154412.csv\n./F1-FAILED/F3/F1-2013-03-19 170411.csv\n./F1-FAILED/F3/F1-2012-09-10 113409.csv\n./F1-FAILED/F2/F1-2011-10-03 113911.csv\n./F1-FAILED/F2/F1-2011-10-02 165908.csv\n./F1-FAILED/F2/F1-2011-10-02 212910.csv\n./F1-ARCHIVE/F1-2012-06-30 004408.csv\n./F1-ARCHIVE/F1-2011-08-17 133905.csv\n./F1-ARCHIVE/F1-2012-10-21 154410.csv\n./F1-ARCHIVE/F1-2012-05-08 190408.csv: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors

4
それはあなたが設定する必要があります見えますIFS=$'\n'、それは各ラインで解析するために`のためのループの前に

回答:


36

ここでforwith を使用するのfindは間違ったアプローチです。たとえば、開いているワームのに関するこの記事を参照してください。

推奨アプローチは使用することですfindwhileそしてreadここで説明されるよう。以下はあなたのために働くはずの例です:

find . -type f -name '*.*' -print0 | 
while IFS= read -r -d '' file; do
    printf '%s\n' "$file"
done

このように、ファイル名をヌル(\0)文字で区切ることにより、スペースやその他の特殊文字の違いが問題を引き起こさないことを意味します。

find見つけたファイルでアーカイブを更新するために、その出力を直接に渡すことができますtar

find . -type f -name '*.*' -printf '%p\0' | 
tar --null -uf archive.tar -T -

アーカイブが存在するかどうかを区別する必要はなく、tar賢明にアーカイブを処理することに注意してください。また、アーカイブにビットが-printf含まれないようにするために、ここを使用することに注意してください./


おかげで、ほとんど機能します。唯一のことは、その./tarをアーカイブすることです。./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser

@Ubuntuser確認する簡単なチェックを追加することができますif [[ "$FILE" == "./" ]]; then continue
キリ

@Ubuntuser:更新された回答./-printf参照して、このビットを回避できます。ただし、現在のディレクトリを参照するだけなので、含まれていてもいなくても違いはありません。また、find/tar使用したい別の組み合わせも含めました。
トール

したいあなたのそれらのためにsortそれらを反復する前に、その結果、あなたが必要ですsort -zヌル区切りのために。
アダムビーン

13

次のforようにループを引用してみてください。

for FILE in "`find . -type f  -name '*.*'`"   # note the quotation marks

引用符がないと、bashはスペースや改行(\n)をまったく処理しません...

また設定してみてください

IFS=$'\n'

1
+1で$ IFS。区切り文字について説明します。
レイ14

1
これは私のために働いた解決策です。commソートされたファイルリストを比較するために使用していましたが、変数が引用されていてもファイル名にスペースが含まれていました。それから、cyberciti.biz / tips / handling-filenames-with-spaces-in-bash.htmlと、IFS = $(echo -en "\ n \ b")で$ IFSを設定する解決策が私のために働いたのを見ました。
pbhj

二重引用符の追加、エレガント、シンプル、美しい-ありがとう!
ビッグリッチ


4

適切な引用に加えてfind、NULLセパレーターを使用するように指示し、結果をwhileループで読み取って処理することができます

while read -rd $'\0' file; do
    something with "$file"
done < <(find  . -type f -name '*.*' -print0)

これは、POSIX準拠のファイル名を処理する必要があります-参照 man find

   -print0
          True; print the full file name on the standard output, followed by a null character (instead of the newline character that  -print  uses).   This  allows  file
          names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output.  This option corresponds to the
          -0 option of xargs.

これは私のために働いた唯一の解決策です。ありがとう。
codefreak 14年


1

スペースを含む可能性のあるファイルを見つけるために、このようなことをしました。

IFS=$'\n'
for FILE in `/usr/bin/find $DST/shared -name *.nsf | grep -v bookmark.nsf | grep -v names.nsf`; do
    file $FILE | tee -a $LOG
done

魅力のように働いた:)


0

ファイル名に改行文字が含まれている場合、ほとんどの回答は中断します。私は15年以上もbashを使用していますが、インタラクティブのみです。

Pythonでは、os.walk()を使用できます:http ://docs.python.org/2/library/os.html#os.walk

また、tarfileモジュール:http : //docs.python.org/2/library/tarfile.html#tar-examples


0

findの-execオプションを使用した方が良いと思います。

find . -type f -name '*.*' -exec tar -cpf archive.tar {} +

Findは、システムコールを使用してコマンドを実行し、スペースと改行が保持されるようにします(特殊文字の引用符が必要なパイプではなく)。「tar -c」は、アーカイブが既に存在するかどうかにかかわらず機能し、(少なくともbashでは){}も+も引用符で囲む必要がないことに注意してください。


-1

minerz029が示唆したように、findコマンドの展開を引用する必要があります。また$FILE、ループ内のすべての置換を引用符で囲む必要があります。

for FILE in "$(find . -type f  -name '*.*')"
do
    if [ ! -f archive.tar ]; then
        tar -cpf archive.tar "$FILE"
    else 
        tar -upf archive.tar "$FILE" 
    fi
done

$()構文はバックティックの使用よりも優先されることに注意してください。このU&Lの質問をご覧ください。また、[[キーワードを削除し、[POSIXであるためコマンドに置き換えました。


それはそうグロブと正規表現のマッチングなどのより新しく、より多くの機能がサポートされます。ただし、ではありません[[[[[[[bashsh
キリ

@ minerz029はい。私が言ってることはそういうことです。[[グロビングをサポートすることの意味がわかりません。グレッグのウィキによると、内部ではグロビングは発生しません[[
ジョセフR.

[ "ab" == a? ] && echo "true"その後、試してください[[ "ab" == a? ]] && echo "true"
キリ

@ minerz029それは大したことではありません。これらは正規表現です(緩やかに解釈されます)。これはグロブではありません。これはa*、「名前が始まり、aその後に任意の数の文字を持つすべてのファイル」ではなく「aの後に任意の数の文字が続く」ことを意味するためです。試してみてください[ ab = a* ] && echo true[[ ab == a* ]] && echo true
ジョセフR.

ああ、[[まだ正規表現を実行[していますが、そうではありません。混乱している必要があります
キリ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.