web-dev-qa-db-ja.com

findコマンドの後にforループ内でexecコマンドを使用するにはどうすればよいですか?

私はこれをしばらくの間機能させようとしています。ファイルのリストを検索し、それらをすべて特定のパスにコピーしたい。

コマンドを個別に実行すると、次のように動作します。

find <path> -name 'file1' -exec cp '{}' <path2> \;

しかし、forループ内で実行できませんでした。

#this works
for f in file1 file2 file3...; do find <path> -name "$f" \; done;
#none of these work (this code tries to find and copy the files named file and list from the path)
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp '{}' <path2> \; done;
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp {} <path2> \; done;

動作しそうにないものをいくつか試しました。コードの引用の最初の行は動けなくなり、他の行は動けなくても何もコピーしません。

検索後にforループ内でexecを使用して何も実行できなかったため、この時点で何をすべきかわかりません。

ファイルを検索して結果を別のファイルに記録し、forループ内でコピーを個別に実行することにより、当面の問題を解決しましたが、これを行う方法を知りたいです。

4
John Hamilton

他の答えは正しいですが、同じディレクトリ構造を繰り返しスキャンするためにfindを複数回呼び出す必要のない異なるアプローチを提供したいと思います。基本的な考え方は、findを使用して

  1. 共通の基準に一致するファイルのリストを生成し、
  2. そのリストにカスタムfilterを適用し、
  3. e。 g。 cp、フィルタリングされたリストのエントリ。

実装1

(ヌルバイトで区切られたレコードを読み取るにはBashが必要です)

find /some/path -type f -print0 |
while read -rd '' f; do
  case "${f##*/}" in
    file1|file2|file3)
      printf '%s\0' "$f";;
  esac
done |
xargs -r0 -- cp -vt /some/other/path --

各パイプは、上記の3つのステップの次のステップの開始に対応しています。

caseステートメントには、 globbing の一致を許可するという利点があります。あるいは、Bashの 条件式 を使用できます。

if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then
  printf '%s\0' "$f"
fi

実装2

一致するファイル名のリストが少し長く、 globbing パターンの小さなセットで置き換えることができない場合、または一致するファイル名のリストが執筆時点で不明な場合、目的のファイル名のリストを保持する array に頼ることができます。

FILE_NAMES=( "file1" "file2" "file3" ... )

find /some/path -type f -print0 |
while read -rd '' f; do
  for needle in "${FILE_NAMES[@]}"; do
    if [ "${f##*/}" = "$needle" ]; then
      printf '%s\0' "$f"
    fi
  done
done |
xargs -r0 -- cp -vt /some/other/path --

実装3

バリエーションとして、 連想配列 を使用できます。これは、単純な「リスト」配列よりもルックアップ時間が速いことを願っています:

declare -A FILE_NAMES=( ["file1"]= ["file2"]= ["file3"]= ... )  # Note the superscripts with []=

find /some/path -type f -print0 |
while read -rd '' f; do
  if [ -v FILE_NAMES["${f##*/}"] ]; then
    printf '%s\0' "$f"
  fi
done |
xargs -r0 -- cp -vt /some/other/path --
4
David Foerster

2つの問題:

  1. for f in some_filesome_fileのコンテンツを反復処理せず、リテラル文字列some_fileを反復処理するか、取得します。これを乗り越えるために、ファイルコンテンツの繰り返し処理のためのforループを忘れるには、適切に実装されたwhileコンストラクトを使用してください。

  2. 単一引用符、この場合は'$f'内に置かれた場合、変数は展開されません。元のアイデアを機能させるには、二重引用符を使用します。

これらをまとめると、ファイル名がfile_listファイル内で改行で区切られていると仮定します。

while IFS= read -r f; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done <file_list

または、ファイルがそのサブディレクトリの下ではなく/path1ディレクトリにあることがわかっている場合は、配列を使用してファイル名を取得し、(GNU)cpを直接使用して、再び改行を仮定することができますfile_list内のファイル名の区切り:

( 
 IFS=$'\n'
 files=( $(<file_list) )
 cp -t /path2 "${files[@]}"
)

膨大な数のファイルがある場合は、配列にダンプするよりも、個別にcpを繰り返し処理する方が適切です。

while IFS= read -r f; do cp -- "$f" /path2; done <file_list

次のようなファイルリストがある場合file1 file2 file3 ...を直接for構成体に追加し、二重引用符を使用するだけでできます。

for f in file1 file2 file3; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done

現在、すべてのファイルがサブディレクトリにない場合は、ここでもcpを直接使用できます。

cp -t /path2 file1 file2 file3

または、cpを使用して、任意のサブディレクトリの下にあるファイルを処理する場合にのみ、静的な絶対パスまたは相対パスを指定できます。

7
heemayl

ファイルリストはテキストファイルではなく、findで検索する一連のファイル名であることを明確にしました。あなたはこれがあなたのために働くと述べました:

for f in file1 file2 file3 ... ; do find <path> -name "$f" \; done;

おそらく、そのコマンドから予想されるファイルのリストを取得することを意味します。

次に、これは機能しないと言います:

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done

おそらく、前にリストされたファイルが<path2>にコピーされないことを意味します。どのエラーが発生しているかはわかりませんが、書かれているとおり、このコマンドが次のようにハングすることを期待しています。

$for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done
>

不足している;を待っています。あなたがその問題を修正したと仮定すると、あなたのコマンドが間違いなく失敗する他の明白な理由は考えられません。あなたはshouldで管理できます

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; ; done

ただし、-tフラグを使用してディレクトリを指定することをお勧めします。 David Foerster for このコメント に感謝する必要があります。これは、cpを使用したmvまたはfind呼び出しに最適な形式を示していると思います。重複ファイルの上書きも拒否します。

for f in file1 file2 file3; do find /path/to/search -name "$f" -exec cp -vt /path/to/destination -- {} + ; done

ノート

  • -v冗長にする-何が行われているかを教えてください
  • -t指定されたディレクトリを宛先として使用
  • --これ以上のオプションを受け入れない
  • +複数の一致がある場合(あなたの場合、これはforがファイルリストの各アイテムに対して1回実行されるため、起こりそうにありませんが、名前ごとに複数の一致するファイルがある場合があります) cpを複数回実行する代わりにcpのリスト
  • ;は、シェル内のコマンドを区切ります。 forループの形式は

    for var in things; do command "$var"; done
    

    doneの前に;が表示されない場合、bashは;または割り込み信号を待ちます。

5
Zanna