web-dev-qa-db-ja.com

必要に応じてバックスラッシュを含むファイルリストを変数に格納するにはどうすればよいですか?

主な質問:

バックアップを実行するスクリプトを書いています。次の方法でファイルのリストを作成します。

LISTOFFILES=$(find ~ \( -name '*.[pP][dD][fF]' -o -name '*.[oO][dD][tT]' \))

変数LISTOFFILESは、多かれ少なかれ次のようなものを格納します。

/home/user/file1.pdf /home/user/file2.odt /home/user/Python Tutorial.pdf

私の次のステップは:

tar cvf backup.tar $LISTOFFILES

Bashの応答は次のとおりです。

/home/user/file1.pdf
/home/user/file2.odt
/home/user/Python: Cannot stat: No such file or directory
tar: Tutorial.pdf

BashはPythonTutorial.pdfが異なるファイルであると想定していることを理解しています。

findにバックスラッシュが含まれているファイルのリストを作成する方法はありますか、それともパイプでlsを使用するように変更する必要がありますか?

ヒントやアドバイスを読みたいのですが。

私の他の質問:

この質問への答えはそれほど重要ではありませんが、1つのヒントが役立つ可能性があります

ファイルパスなしでファイル名のみを含むファイルを一覧表示するにはどうすればよいですか?

例:/home/user/fileの代わりにfile

findおよびPruneオプションを指定してpathコマンドを試しましたが、成功しませんでした。

6
omar

あなたの主な質問に関しては、あなたは別のアプローチを取る必要があるでしょう。あなたの方法は、すべてのファイルを$ LISTOFFILESの単一の文字列に入れることです。次に、アクセスするときに、引用符を使用していないため、スペースで分割され、表示されている結果が得られます。

必要な結果を得る最も簡単な方法は次のとおりです。

find ~ \( -name '*.[pP][dD][fF]' -o -name '*.[oO][dD][tT]' \) -print0 | xargs -0 tar cvf backup.tar

そこで行ったのは、findを実行し、各結果をNULL文字(-print0部分)で区切ることでした。次に、findの出力がxargsにパイプされ、NULL文字で区切られたファイルのリストが読み込まれ(-0引数はxargsにこれを行うように指示します)、そのファイルのリストを引数として 'tar'に渡します。

2番目の質問:findコマンドからのみファイル名を取得する場合は、printfを使用します

find /path/to -printf '%f\n'
file.txt

一般的に、findだけでなく、basenameを使用するのが最も簡単です。

# basename /path/to/file.txt
file.txt

basenameは、まさにそれを行うように設計されており、他には何もありません。

7
Patrick

あなたの試みが失敗した理由とそれを修正する方法

findによって返されるファイル名のリストを蓄積できますが、ファイル名に改行がない場合に限り、予防策を講じる必要があります。

ここで_$LISTOFFILES_のように、引用符で囲まれていないコマンド置換または変数展開を記述すると、シェルは結果に対して 単語分割 および グロブ(ワイルドカード展開) を実行します。

規則:コマンドの置換と変数の展開は常に二重引用符で囲みます。
例外:二重引用符を省略する必要がある理由と、そうしても安全な理由を理解している場合。

ここでは、二重引用符を省略する必要があります。これは、単語の分割が文字列を改行で区切られたビットに分割するためです。ただし、デフォルトでは、他の空白文字でも分割されます。したがって、それを安全にし、グロブもオフにする必要があります。前者は IFS variable を単一の改行に設定し、後者は _set -f_ を呼び出すことで実現します。

_IFS='
'
set -f
tar cvf backup.tar $LISTOFFILES
_

バックスラッシュは含まれないことに注意してください。バックスラッシュは、ファイル名を直接入力するときに使用されます。findなどのコマンドによって生成される場合は使用されません。

findを使用する通常の方法

findの出力を解析するのではなく、_-exec_アクションを使用して、ファイルに対して実行するコマンドを呼び出すようにする必要があります。これにより、ファイルが多すぎてコマンドラインの最大長を超えた場合に対処できます。プログラムは必要な回数だけ呼び出されます。

ここでは、_tar -c_を複数回呼び出すことができないため、これは少し難しいです(呼び出しごとに新しい空のアーカイブから最初からやり直します)。ただし、 ディレクトリ内のすべてのPDFを切り上げ、ディレクトリ構造を保持する を参照してください

より簡単な方法

ファイルが多すぎず、ksh93またはbash≥4またはzshを実行している場合は、_**_ワイルドカードを使用してサブディレクトリに再帰します。最初にいくつかのシェルオプションを設定する必要があります。

_set -o globstar                  # on ksh93
shopt -s globstar -s extglob     # on bash 4
setopt ksh_glob                  # on zsh
tar cvf backup.tar ~/**/*.@(PDF|pdf|ODT|odt)
_

Zshでは、特別な設定なしで、そのパターンを~/**/*.(PDF|pdf|ODT|odt)として記述できます。 _setopt extglob_の後に、~/**/(#i)*.(pdf|odt)と書くことができます。

または、 pax を使用できます:

_pax -w -x ustar -s '/\.pdf$/&/' -s '/\.PDF$/&/' -s '/\.odt$/&/' -s '/\.ODT$/&/' -s '/.*//' ~ >backup.tar
_

パスプレフィックスが必要ない場合

tarpaxにはいくつかの方法がありますが、最も簡単な方法は、最初にアーカイブするディレクトリに変更することです。

_( IFS='
'; set -f; cd ~ && tar cvf /path/to/backup.tar $(find . …) )
( cd ~ && pax -w … . ) >backup.tar
_

最初の質問:

最初の質問に対する答えはquotesです。

-> tar cvf backup.tar $LISTOFFILESを実行する代わりに

これを行う-> tar cvf backup.tar "$LISTOFFILES"

例を見てみましょう(このデモ用にファイルの1つを使用しましたが、ファイルがコンピューターに存在しない場合でも、引用符の有無にかかわらず発生するエラーを確認してください)-

[jaypal:~] file="/home/user/Python Tutorial.pdf"

[jaypal:~] ls $file
ls: /home/user/Python: No such file or directory
ls: Tutorial.pdf: No such file or directory

/home/user/PythonTutorial.pdfの2つのファイルでエラーが発生しました。 quotevariableに入れて、何が得られるか見てみましょう。

[jaypal:~] ls "$file"
ls: /home/user/Python Tutorial.pdf: No such file or directory

ファイル/home/user/Python Tutorial.pdfでエラーが1回だけ発生しました

2番目の質問:

/path/to/fileを整理するには、いくつかの方法があります。私のお気に入りはawkですが、sed用にも紹介します。

sedメソッド:

[jaypal:~/Temp] echo "$filename"
/home/user/file1.pdf /home/user/file2.odt /home/user/Python Tutorial.pdf

[jaypal:~/Temp] echo "$filename" | sed 's@/home/user/@@g'
file1.pdf file2.odt Python Tutorial.pdf

このようにスクリプトに含めることができます-

LISTOFFILES=$(find ~ \( -name '*.[pP][dD][fF]' -o -name '*.[oO][dD][tT]' \) | sed 's@/home/user/@@g' )

ここで覚えておくべきことは、substitutionを実行しているということです。 /home/user/nothingに置き換えています。複数のディレクトリからファイルをfindingしている場合は、何トンもの置換を行うか(yikes!)、awkの方法で行うことができます。

awkメソッド:

awk -F"/" '{print $NF}'

うん、それだけです。任意のディレクトリまたはそのサブディレクトリの下にあるfindファイルに対して機能します。

したがって、スクリプトは次のようにこれを行うことができます-

LISTOFFILES=$(find ~ \( -name '*.[pP][dD][fF]' -o -name '*.[oO][dD][tT]' \) | awk -F"/" '{print $NF}')

その他のヒントとコツ:

ここに私があなたに役立つかもしれないと思ういくつかのランダムなヒントがあります-

findファイル名insensitivelyには、現在のファイル名の代わりにinameを使用します。これをチェックしてください-

[jaypal:~/Temp] find . -name "j*.txt"
./jP.txt

[jaypal:~/Temp] find . -iname "j*.txt"
./jj.TXT
./jP.txt

/path/to/fileをプルーニングする他の方法は、basenameを使用することです。例を挙げますが、快適に感じるようにスクリプトに組み込むことができます。

[jaypal:~/Temp] filename="/usr/bin/Java.pdf"

[jaypal:~/Temp] echo "$filename"
/usr/bin/Java.pdf

[jaypal:~/Temp] basename "$filename"
Java.pdf

お役に立てれば!

2
jaypal singh