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

时间:2008-10-19 21:16:26

标签: bash shell pattern-matching glob

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

cp [exclude-matches] *Music* /target_directory

应该采用什么来取代[排除匹配]来实现这一目标?

11 个答案:

答案 0 :(得分:344)

在Bash中,您可以通过启用extglob选项来完成此操作(将ls替换为cp并添加目标目录)

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

答案 1 :(得分:213)

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

您使用shopt -s extglob打开它,然后使用shopt -u extglob将其关闭。

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

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

完整的 ext 结束 glob bing运算符(摘自man bash):

  

如果使用内置的shopt启用了extglob shell选项,则可以扩展几个   模式匹配运算符得到认可。在以下描述中,轻拍   tern-list是由|分隔的一个或多个模式的列表。复合图案   可以使用以下子模式中的一个或多个来形成:

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

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

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

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

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

答案 2 :(得分:22)

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

cp `ls | grep -v Music` /target_directory

我知道这不是你想要的,但它会解决你的例子。

答案 3 :(得分:7)

如果你想避免使用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/

答案 4 :(得分:5)

在bash中,shopt -s extglob的替代方法是GLOBIGNORE variable。它并不是更好,但我发现它更容易记住。

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

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

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

答案 5 :(得分:4)

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

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

答案 6 :(得分:4)

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

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

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

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

答案 7 :(得分: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

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

编辑:Adam在评论中指出这是递归的。查找选项mindepth和maxdepth可用于控制它。

答案 8 :(得分:3)

我尚未在此处看到的不使用extglobfindgrep的技巧是将两个文件列表视为集合并“diff “他们使用comm

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

comm优于diff,因为它没有额外的瑕疵。

这将返回第1组中ls的所有元素,这些元素在第2组ls *Music*中也。这要求两个集合按排序顺序才能正常工作。 ls和glob扩展没问题,但如果您使用find之类的内容,请务必调用sort

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

可能有用。

答案 9 :(得分:2)

以下作品列出了当前目录中的所有*.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. 在第三行中,将跳过与case模式[0-9]*)行匹配的所有文件(continue语句跳转到for的下一次迭代环)。 - 如果你愿意,你可以在这里做一些更有趣的事情,例如比如使用[!a-z]*跳过所有不以字母(a-z)开头的文件,或者你可以使用多种模式跳过几种文件名,例如[0-9]*|*.bak要跳过.bak个文件以及不以数字开头的文件。

答案 10 :(得分:0)

这样做可以完全排除'音乐'

cp -a ^'Music' /target

这和那些排除音乐等内容?*或*?音乐

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