查找并复制包含文件类型的目录

查找并复制包含文件类型的目录

我有一个目录“电影”,其中包含子目录“电影名称”。每个子目录“电影名称”包含一个电影文件和相关的图像/nfo文件等。

我正在尝试将包含“.avi”类型电影文件的所有目录复制到外部 USB 设备。

因此,如果Directory/subdirectory A包含movie.aviposter.jpgmovie.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 -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

答案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
  • forawk能够处理 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

您可以使用xargscp来完成这项工作:

find . -name "*.avi" -printf "%h\n" | xargs cp -t /share/USBDisk1/Movies/ -r

相关内容