如何在unix / linux shell中进行模式匹配时使用反向或负向通配符?

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

cp [exclude-matches] *Music* /target_directory 

应该用什么来代替[排除比赛]来完成呢?

在Bash中,你可以通过启用extglob选项来实现,就像这样(用csreplacels,然后添加目标目录)

 ~/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 

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

你用shopt -s extglob打开它,用shopt -u extglob把它关掉。

在你的例子中,你最初会做:

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

全部可用的分机结束操作符(摘自man bash ):

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

  • ?(模式列表)
    匹配给定模式的零次或一次出现
  • *(模式列表)
    匹配零个或多个出现的给定模式
  • +(模式列表)
    匹配一个或多个出现的给定模式
  • @(模式列表)
    匹配一个给定的模式
  • !(模式列表)
    匹配任何东西,除了一个给定的模式

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

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

当然,正常的shell会起作用,所以最后一个例子也可以写成:

 $ ls -d !(*.[ch]) 

不是在bash(我知道),但:

 cp `ls | grep -v Music` /target_directory 

我知道这不是你正在寻找的,但它会解决你的例子。

如果你想避免使用exec命令的成本,我相信你可以用xargs做的更好。 我认为以下是一个更有效的select

 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/ 

你也可以使用一个非常简单的for循环:

 for f in `find . -not -name "*Music*"` do cp $f /target/dir done 

我个人的偏好是使用grep和while命令。 这使得人们可以编写function强大且可读的脚本,确保您最终能够按照自己的意愿进行操作。 另外,通过使用回声命令,您可以在执行实际操作之前执行空运行。 例如:

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

将打印出您将最终复制的文件。 如果列表正确,则下一步是简单地使用copy命令replaceecho命令,如下所示:

 ls | grep -v "Music" | while read filename do cp "$filename" /target_directory done 

一个解决scheme可以findfind。

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

查找有很多选项,你可以对你包含和排除的内容进行非常具体的描述。

编辑:亚当在评论中指出,这是recursion的。 find选项mindepth和maxdepth可以用来控制这个。

在bash中,替代shopt -s extglobGLOBIGNOREvariables 。 这不是更好,但我觉得更容易记住。

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

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

完成后, unset GLOBIGNORE ,以便能够在源目录中rm *techno*

这将做到不包括确切'音乐'

 cp -a ^'Music' /target 

这和排除像音乐?*或*?音乐的东西

 cp -a ^\*?'complete' /target cp -a ^'complete'?\* /target 

我在这里还没有看到一个不使用extglobfind或者grep extglob ,就是把两个文件列表当作集合,用diff“比较”它们:

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

commdiff更可取,因为它没有额外的东西。

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

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

可能有用。

以下工作列出当前目录中的所有*.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循环遍历以.txt结尾的/some/dir的所有文件。

  2. 在第二行case语句是用来淘汰不需要的文件。 – ${FILE##*/}expression式从文件名(这里是/some/dir/ )中剥离出任何前导目录名组件,这样patters只能匹配文件的基本名称。 (如果只是根据后缀清除文件名,可以将其缩短为$FILE 。)

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