我有一个目录“电影”,其中包含子目录“电影名称”。每个子目录“电影名称”包含一个电影文件和相关的图像/nfo文件等。
我正在尝试将包含“.avi”类型电影文件的所有目录复制到外部 USB 设备。
因此,如果Directory/subdirectory A
包含movie.avi
、 poster.jpg
和movie.nfo
,那么我想将该目录及其内容复制到外部驱动器。
我试过这个:
find . -name "*.avi" -printf "%h\n" -exec cp {} /share/USBDisk1/Movies/ \;
但它只复制文件,而不复制目录和文件内容。
答案1
首先,为什么您的尝试不起作用:-printf "%h\n"
打印文件名的目录部分.avi
。这不会影响后续-exec
操作中的任何内容 -{}
并不意味着“无论最后printf
打印的命令是什么”,它意味着“找到的文件的路径”。
如果要在该cp
命令中使用文件名的目录部分,则需要在将文件名传递给cp
.该find
命令没有此功能,但 shell 有,因此 makefind
调用一个 shell,它会调用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
答案2
由于您似乎正在使用 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
) - 因为
sort
因为-z
- for
awk
能够处理 NUL 字符 - for
xargs
(对于-r
和-0
选项,尽管一些 BSD 也支持它们) cp
(为了-t
)
这个想法是:
find
输出所有文件的目录名avi
。- 我们对该列表进行排序,以便每个目录都列在其父目录之后(因为我们需要该
/
字符在任何其他目录之前排序,这就是我们将其与 交换的原因\1
)。 - 我们使用
sort -u
这样每个目录只出现一次 - 我们使用一个小脚
awk
本来删除已包含祖先的每个目录条目(因为我们不想复制两者,./a
并且./a/b
如果两者都包含 AVI 文件)。 - 将该列表传递给
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/ \;
但这意味着调用一个bash
并重做每个目录中的文件列表bash
,因此可能比以前的解决方案效率更低。
答案3
我发现这个我认为更简单并且几乎可以满足您的需求:
find . -name "*.avi" -exec cp "{}" /share/USBDisk1/Movies/ \;
我认为与您所做的类似,但没有-printf
捕获"{}"
find 的输出。
参考:https://stackoverflow.com/questions/16898462/moving-files-from-directory-and-all-subdirectories
最好的,
答案4
您可以使用xargs
和cp
来完成这项工作:
find . -name "*.avi" -printf "%h\n" | xargs cp -t /share/USBDisk1/Movies/ -r