サブディレクトリ「映画名」を含むディレクトリ「映画」があります。各サブディレクトリ「MovieName」には、ムービーファイルと関連するimage/nfoファイルなどが含まれています。
タイプ「.avi」のムービーファイルを含むすべてのディレクトリを外部USBデバイスにコピーしようとしています。
したがって、Directory/subdirectory A
にmovie.avi
、poster.jpg
、movie.nfo
が含まれている場合、そのディレクトリとその内容を外部ドライブにコピーします。
私はこれを試しました:
find . -name "*.avi" -printf "%h\n" -exec cp {} /share/USBDisk1/Movies/ \;
ただし、ファイルをコピーするだけで、ディレクトリとファイルの内容はコピーしません。
まず、試みがうまくいかない理由:-printf "%h\n"
は、.avi
ファイル名のディレクトリ部分を出力します。それは、後続の-exec
アクションの何にも影響しません— {}
は、「最後に表示されたprintf
コマンドが何であっても」という意味ではなく、「見つかったファイルへのパス」を意味します。
そのcp
コマンドでファイル名のディレクトリ部分を使用する場合は、ファイル名をcp
に渡す前に変更する必要があります。 find
コマンドにはこの機能はありませんが、シェルにはあります。そのため、find
にcp
を呼び出すシェルを呼び出させます。
find . -name "*.avi" -exec sh -c 'cp -Rp "${0%/*}" /share/USBDisk1/Movies/' {} \;
ディレクトリをコピーするため、-r
をcp
に渡す必要があることに注意してください。ファイルの変更時刻などのメタデータも-p
で保持する必要があります。
cp -Rp
をrsync -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
内のファイルリストをやり直すことを意味します。そのため、おそらく前のソリューションよりも効率が悪くなります。
私はこれがあなたが必要とするものよりはるかに単純でかなり多くの結果であると私が思うものを見つけました:
find . -name "*.avi" -exec cp "{}" /share/USBDisk1/Movies/ \;
私はあなたと同じように思いますが、-printf
として"{}"
は、findからの出力をキャプチャします。
リファレンス: https://stackoverflow.com/questions/16898462/moving-files-from-directory-and-all-subdirectories
ベスト、
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ファイルが含まれている場合はそれらをコピーすることをお勧めします。