web-dev-qa-db-ja.com

ファイルタイプを含むディレクトリを検索してコピーします

サブディレクトリ「映画名」を含むディレクトリ「映画」があります。各サブディレクトリ「MovieName」には、ムービーファイルと関連するimage/nfoファイルなどが含まれています。

タイプ「.avi」のムービーファイルを含むすべてのディレクトリを外部USBデバイスにコピーしようとしています。

したがって、Directory/subdirectory Amovie.aviposter.jpgmovie.nfoが含まれている場合、そのディレクトリとその内容を外部ドライブにコピーします。

私はこれを試しました:

find . -name "*.avi" -printf "%h\n" -exec cp {} /share/USBDisk1/Movies/ \;

ただし、ファイルをコピーするだけで、ディレクトリとファイルの内容はコピーしません。

4
Lowey

まず、試みがうまくいかない理由:-printf "%h\n"は、.aviファイル名のディレクトリ部分を出力します。それは、後続の-execアクションの何にも影響しません— {}は、「最後に表示されたprintfコマンドが何であっても」という意味ではなく、「見つかったファイルへのパス」を意味します。

そのcpコマンドでファイル名のディレクトリ部分を使用する場合は、ファイル名をcpに渡す前に変更する必要があります。 findコマンドにはこの機能はありませんが、シェルにはあります。そのため、findcpを呼び出すシェルを呼び出させます。

find . -name "*.avi" -exec sh -c 'cp -Rp "${0%/*}" /share/USBDisk1/Movies/' {} \;

ディレクトリをコピーするため、-rcpに渡す必要があることに注意してください。ファイルの変更時刻などのメタデータも-pで保持する必要があります。

cp -Rprsync -aに置き換えると便利です。そうすれば、ムービーディレクトリをすでにコピーしている場合、そのディレクトリは再度コピーされません(内容が変更されていない限り)。

コマンドには、影響する場合と影響しない場合がある欠陥があります。ディレクトリに複数の.aviファイルが含まれている場合、そのコマンドは複数回コピーされます。 .aviファイルを探すよりも、ディレクトリを探して、それらに.aviファイルが含まれている場合はコピーする方がよいでしょう。 rsyncの代わりにcpを使用すると、ファイルは再度コピーされません。rsyncがファイルの存在を何度も確認するのは、少し手間がかかります。 。

すべての映画ディレクトリが最上位ディレクトリのすぐ下にある場合は、findは必要ありません。ワイルドカード・パターンを単純にループするだけで十分です。

for d in ./*/; do
  set -- "$d/"*.avi
  if [ -e "$1" ]; then
    # there is at least one .avi file in $d
    cp -rp -- "$d" /share/USBDisk1/Movies/
  fi
done

ムービーディレクトリがネストされている場合(例:Movies/science fiction/Ridley Scott/Blade Runner)、findは必要ありません。ワイルドカードパターンの単純なループで十分です。 ksh93またはbash≥4またはzshを実行する必要があり、ksh93では最初にset -o globstarを実行する必要があり、bashでは最初にshopt -s globstarを実行する必要があります。ワイルドカードパターン**/は、現在のディレクトリとそのすべてのサブディレクトリに再帰的に一致します(bashはサブディレクトリへのシンボリックリンクもトラバースします)。

for d in ./**/; do
  set -- "$d/"*.avi
  if [ -e "$1" ]; then
    cp -rp -- "$d" /share/USBDisk1/Movies/
  fi
done

GNUツールを使用しているようなので、次のことができます。

find . -name '*.avi' -printf '%h\0' |
  tr '\1/' '/\1' |
  LC_ALL=C sort -zu |
  tr '\1/' '/\1' |
  awk -vRS='\0' -vORS='\0' '
    NR>1 && substr($0, 1, length(l)) == l {next}
    {print; l=$0"/"}' |
  xargs -r0 cp -rt /share/USBDisk1/Movies/

上記はGNU具体的です:

  • findの場合(-printfのため)
  • -zのためにsortの場合
  • awkの場合はNUL文字を処理する機能
  • xargsの場合(-rおよび-0オプションの場合。ただし、一部のBSDはそれらもサポートしています)
  • cp-t用)

アイデアは次のとおりです。

  • findは、すべてのaviファイルのディレクトリ名を出力します。
  • そのリストをソートして、すべてのディレクトリがその親ディレクトリの後にリストされるようにします(そのためには、/文字を他の文字の前にソートする必要があるため、\1と入れ替えます)。
  • sort -uを使用して、各ディレクトリが1回だけ表示されるようにします
  • 小さなawkスクリプトを使用して、祖先がすでに含まれているすべてのディレクトリエントリを削除します(両方にAVIファイルが含まれている場合、./a./a/bの両方をコピーしたくないため) )。
  • そのリストをxargsのstdinに渡して、引数としてcp -t targetに渡すことができるようにします。

同じ名前の後にいくつかのディレクトリがある場合、潜在的な衝突から保護しようとしないことに注意してください。

ターゲットドライブのディレクトリ構造を再現する場合は、xargs -r0 cp -rt /share/USBDisk1/Movies/を次のように置き換えることができます。

pax -0rw /share/USBDisk1/Movies/

paxは、少なくともDebianまたはMirBSDで見られるような-0オプションをサポートしています)。

理想的には、次のように記述できるようにしたいと思います。

find . -type d -has '*.avi' -Prune -exec cp -rt /target {} +

残念ながら、findにはそのような-has述語はありません。あなたが達成できる最も近いものは次のようになります:

find . -type d -exec bash -c '
  shopt -s nullglob dotglob
  set -- "$1"/*.avi
  (( $# > 0 ))' sh {} \; \
  -Prune -exec cp -r {} /share/USBDisk1/Movies/ \;

しかし、これは、1つのbashを呼び出し、各ディレクトリのbash内のファイルリストをやり直すことを意味します。そのため、おそらく前のソリューションよりも効率が悪くなります。

1

私はこれがあなたが必要とするものよりはるかに単純でかなり多くの結果であると私が思うものを見つけました:

find . -name "*.avi" -exec cp "{}" /share/USBDisk1/Movies/ \;

私はあなたと同じように思いますが、-printfとして"{}"は、findからの出力をキャプチャします。

リファレンス: https://stackoverflow.com/questions/16898462/moving-files-from-directory-and-all-subdirectories

ベスト、

0
foo () 
{ 
    local subdirs=() target="$1" dest="$2";
    while IFS= read -rd ''; do
        subdirs+=("$REPLY");
    done < <(find "$target" -type f -iname '*.avi' -exec bash -c 'printf "%s\0" "${@%/*}"' _ {} + | sort -zu);
    cp -rp -- "${subdirs[@]}" "$dest"
}

使用法:foo <source directory> <destination directory>

これは、Gillesが以前に言及した次の問題にも対処します。

コマンドに欠陥があるか、または影響がない可能性があります。ディレクトリに複数の.aviファイルが含まれている場合、複数回コピーされます。 .aviファイルを探すよりも、ディレクトリを探して、ディレクトリに.aviファイルが含まれている場合はそれらをコピーすることをお勧めします。

0
Rany Albeg Wein