360

假设我想复制一个目录的内容,不包括名称中包含“音乐”一词的文件和文件夹。

cp [exclude-matches] *Music* /target_directory

应该用什么代替 [exclude-matches] 来实现这一点?

4

11 回答 11

408

在 Bash 中,您可以通过启用该extglob选项来做到这一点,就像这样(当然,替换lscp添加目标目录)

~/foobar> shopt extglob
extglob        off
~/foobar> ls
abar  afoo  bbar  bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob  # Enables extglob
~/foobar> ls !(b*)
abar  afoo
~/foobar> ls !(a*)
bbar  bfoo
~/foobar> ls !(*foo)
abar  bbar

您可以稍后禁用 extglob

shopt -u extglob
于 2008-10-19T21:32:42.253 回答
247

shell 选项在extglob命令行中为您提供更强大的模式匹配。

你用 开启它,用shopt -s extglob关闭它shopt -u extglob

在您的示例中,您最初会执行以下操作:

$ shopt -s extglob
$ cp !(*Music*) /target_directory

完整可用的扩展通配符运算符是(摘自man bash):

如果使用内置 shopt 启用了 extglob shell 选项,则可以识别多个扩展模式匹配运算符。模式列表是由 | 分隔的一个或多个模式的列表。 可以使用以下子图案中的一种或多种来形成复合图案:

  • ?(pattern-list)
    匹配给定模式的零次或一次出现
  • *(pattern-list)
    匹配给定模式的零次或多次出现
  • +(pattern-list)
    匹配给定模式的一次或多次出现
  • @(pattern-list)
    匹配给定模式之一
  • !(pattern-list)
    匹配除给定模式之一之外的任何内容

因此,例如,如果您想列出当前目录中所有不是.c.h文件的文件,您可以这样做:

$ ls -d !(*@(.c|.h))

当然,正常的 shell globing 是有效的,所以最后一个例子也可以写成:

$ ls -d !(*.[ch])
于 2008-10-20T00:15:43.097 回答
26

不在 bash 中(据我所知),但是:

cp `ls | grep -v Music` /target_directory

我知道这不完全是您想要的,但它会解决您的示例。

于 2008-10-19T21:23:42.913 回答
10

如果您想避免使用 exec 命令的 mem 成本,我相信您可以使用 xargs 做得更好。我认为以下是更有效的替代方案

find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec



find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/
于 2008-10-19T21:40:26.780 回答
7

我还没有在这里看到的一个技巧,它不使用extglob, find,或者grep将两个文件列表视为集合并使用“区分”它们comm

comm -23 <(ls) <(ls *Music*)

comm比它更可取,diff因为它没有多余的东西。

ls这将返回集合 1,中不在集合 2, 中的所有元素ls *Music*。这需要两个集合都按排序顺序才能正常工作。和 glob 扩展没问题ls,但如果你使用类似的东西find,一定要调用sort.

comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)

潜在有用。

于 2017-03-13T17:59:25.193 回答
6

您还可以使用一个非常简单的for循环:

for f in `find . -not -name "*Music*"`
do
    cp $f /target/dir
done
于 2008-10-19T21:32:27.997 回答
6

在 bash 中shopt -s extglobGLOBIGNORE变量. 它并不是真的更好,但我发现它更容易记住。

一个可能是原始海报想要的示例:

GLOBIGNORE="*techno*"; cp *Music* /only_good_music/

完成后,unset GLOBIGNORE就可以rm *techno*在源目录中了。

于 2015-11-28T18:36:45.120 回答
4

我个人的偏好是使用 grep 和 while 命令。这允许人们编写功能强大但可读的脚本,以确保您最终完全按照您的意愿行事。另外,通过使用 echo 命令,您可以在执行实际操作之前执行空运行。例如:

ls | grep -v "Music" | while read filename
do
echo $filename
done

将打印出您最终要复制的文件。如果列表正确,下一步就是简单地将 echo 命令替换为 copy 命令,如下所示:

ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done
于 2011-02-18T09:35:26.347 回答
3

可以使用 find 找到一个解决方案。

$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt

Find 有很多选项,您可以非常具体地了解包含和排除的内容。

编辑:评论中的亚当指出这是递归的。查找选项 mindepth 和 maxdepth 可用于控制这一点。

于 2008-10-19T21:23:13.473 回答
3

以下作品列出*.txt了当前目录中的所有文件,但以数字开头的文件除外。

这适用于bashdashzsh所有其他 POSIX 兼容的 shell。

for FILE in /some/dir/*.txt; do    # for each *.txt file
    case "${FILE##*/}" in          #   if file basename...
        [0-9]*) continue ;;        #   starts with digit: skip
    esac
    ## otherwise, do stuff with $FILE here
done
  1. 在第一行,该模式/some/dir/*.txt将导致for循环遍历/some/dir名称以 . 结尾的所有文件.txt

  2. 在第二行中,case 语句用于清除不需要的文件。–${FILE##*/}表达式从文件名(此处/some/dir/)中删除任何前导目录名称组件,以便模式只能匹配文件的基本名称。(如果您只是根据后缀清除文件名,则可以将其缩短为$FILE。)

  3. 在第三行中,所有匹配casepattern [0-9]*) 行的文件都将被跳过(continue语句跳转到for循环的下一次迭代)。– 如果您愿意,您可以在这里做一些更有趣的事情,例如使用 跳过所有不以字母 (a-z) 开头的文件[!a-z]*,或者您可以使用多种模式来跳过多种文件名,例如[0-9]*|*.bak跳过两个文件的.bak文件,以及不以数字开头的文件。

于 2011-02-28T09:24:39.077 回答
1

这将完全排除“音乐”

cp -a ^'Music' /target

this 和 that 用于排除诸如 Music?* 或 *?Music 之类的内容

cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target
于 2011-02-12T18:06:50.543 回答