cp -r * 除了不复制任何 .pdf 文件 - 复制目录子树同时排除具有给定扩展名的文件

cp -r * except dont copy any .pdf files - copy a directory subtree while excluding files with a given extension

编者注:在问题的原始形式中,复制整个 子树 的方面并不明显。

如何将所有文件从一个目录子树复制到另一个目录子树但忽略一种类型的所有文件?

bash 是否处理正则表达式?

类似于:cp -r !*.pdf /var/www/ .?

编辑 1

我有一个查找表达式:find /var/www/ -not -iname "*.pdf"

这列出了我要复制的所有文件。我如何将其通过管道传输到复制命令?

编辑 2

只要参数列表不太长,这就有效:

sudo cp `find /var/www/ -not -iname "*.pdf"` .

编辑 3

但有一个问题是我 运行 遇到丢失目录结构的问题。

是一个简洁的解决方案,很好地反映了意图,并且如果应排除 多个 扩展,则易于扩展。

尝试 单独使用 POSIX 实用程序和 POSIX 兼容选项 需要稍微不同的方法:

cp -pR /var/www/. . && find . -name '*.pdf' -exec rm {} +

换句话说:首先复制整个子树,然后从 destination 子树中删除所有 *.pdf 文件。

注:

  • -p 保留原始文件在文件时间戳、所有权和权限位方面的属性(tar 似乎默认这样做);如果没有 -p,副本将归当前用户所有并接收新的时间戳(尽管保留了权限位)。

  • 使用 cptar 有一个优势:您可以更好地控制源文件中 symlinks 的处理方式,通过 -H-L-P 选项 - 请参阅 POSIX spec. for cp.

    • tar 似乎总是按原样复制符号链接。
  • -R 取代了 cp 的遗留 -r 选项,因为后者对非常规文件的行为定义不明确 - 请参阅 RATIONALE POSIX spec. for cp

  • 部分
  • 不区分大小写匹配的 -iname-delete 都不是 POSIX spec. for find 的一部分,但是 GNU find 和 BSD/macOS find支持他们

  • 注意源路径 /var/www/. 如何以 /. 结束,以确保其 内容 被复制到目标路径(相反将所有内容放入 www 子文件夹)。

    • 使用 BSD cp/var/www/(尾随 /)也可以,但是 GNU cp 对待 /var/www/var/www/ 相同。

至于你的问题和解答尝试

Does bash handle regex?

在文件名扩展(globbing)的上下文中,Bash 只理解 patterns,不理解正则表达式(Bash 确实有 =~ 字符串的正则表达式匹配运算符但是,在 [[ ... ]] 条件句中匹配。

作为非标准扩展,Bash 实现了 extglob shell 选项,adds additional constructs to the pattern-matching notation 允许更复杂的匹配,例如 !(...) 否定 匹配项,这就是您要查找的内容。

如果将其与另一个非标准 shell 选项 globstar**、Bash v4+)结合使用,您可以构建一个匹配所有项目的单一模式 除了一个给定的子模式跨越整个子树:

/var/www/**/!(*.pdf)

/var/www/ 的子树中找到所有非 PDF 文件系统项目。

但是,将该模式与 cp 结合使用将无法按预期工作:与 -R 结合使用任何子目录。仍然完整复制;没有 -R,子目录。完全被忽略了。

注意事项

  • 默认情况下,模式(glob)忽略隐藏项,除非明确匹配(*将只匹配非隐藏项)。要包括它们,请先设置 shell 选项 dotglob

  • 匹配默认大小写敏感;打开 shell 选项 nocaseglob 使其不区分大小写。


find /var/www/ -not -iname "*.pdf" 本质上与上面的扩展 glob 产生相同的结果,除了不区分大小写的匹配、总是包含隐藏项以及输出路径(通常)顺序不同。

然而,复制输出路径到它们的预定目的地是重要的部分:您必须构建类似的子目录。在目标目录中。在运行中,您必须分别为每个输入路径执行此操作,这也将非常慢。

您自己的尝试 sudo cp `find /var/www/ -not -iname "*.pdf"` . 在几个方面存在不足:

  • 正如您自己发现的那样,这会将所有匹配项复制到单个目标目录中。

  • 命令替换的输出 `...` 受 shell 扩展的影响,即分词和文件名扩展,这可能会破坏命令,尤其是文件名嵌入空格。

  • 注意:正如所写,所有目标项目都将归 root 用户所有。

很遗憾,

Bash 帮不上忙。

许多人使用 tarrsync 来完成此类任务,因为他们每个人都能够递归复制文件,并且每个人都提供一个 --exclude 参数来排除某些文件名模式。 tar 更有可能安装在给定的计算机上,所以我会向您展示。

假设您当前位于目标目录中, shell 命令:

tar -cC /var/www . | tar -x

将递归地从/var/www复制所有文件到当前目录。

要过滤掉 PDF 文件,请使用:

tar -cC /var/www --exclude '*.pdf' . | tar -x

可以给出多个 --exclude 个参数,因此:

tar -cC /var/www --exclude '*.pdf' --exclude '*.txt' . | tar -x

也会排除 .txt 文件。

编辑 根据下面@mklement0 的评论,这些解决方案不适合目录树递归——它们只能在一个目录上工作,按照 OP 的原始形式问题。

@rorschach。是的,你可以做到。

使用cp:

设置您的 Bash shell 的 extglob 选项并输入:

shopt -s extglob #You can set this in your shell startup to enable it by default
cp /var/www/!(*.pdf) .

如果您想关闭(取消设置)此(或任何其他)shell 选项,请使用:

shopt -u extglob #or whatever shell option you wish to unset

使用find

如果您更喜欢使用 find,您可以使用 xargs 来执行您希望 Bash 执行的操作:

find /var/www/ ! -iname "*.pdf" -maxdepth 1 | xargs -I{} cp {} .