我如何使用逆或负通配符时模式匹配在unix/linux shell?

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

cp [exclude-matches] *Music* /target_directory

应该用什么来代替[排除匹配]来实现这一点?

217298 次浏览

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有相当多的选项,你可以非常具体地包括和排除什么。

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

不是bash(据我所知),而是:

cp `ls | grep -v Music` /target_directory

我知道这不是你想要的,但它能解决你的问题。

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

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

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

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

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

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

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

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

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

完全可用的以__abc1end的一团bing操作符是(摘自man bash):

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

    <李> ? (pattern-list) < br > 匹配0或1个给定模式 <李> * (pattern-list) < br > 匹配给定模式的零次或多次出现 <李> + (pattern-list) < br > 匹配给定模式的一次或多次出现 <李> @ (pattern-list) < br > 匹配一个给定的模式 <李> ! (pattern-list) < br > 匹配除之外的任何给定模式

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

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

当然,普通的shell globing也可以,所以最后一个例子也可以写成:

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

这样就可以排除“音乐”

cp -a ^'Music' /target

排除音乐之类的东西?*还是*?音乐

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

我个人倾向于使用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

下面的作品列出了当前目录下的所有*.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/)中剥离任何领先的dir name组件,以便模式只能匹配文件的basename。(如果你只是根据后缀来清除文件名,你可以把它缩短为$FILE。)

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

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

下面的例子可能就是最初的海报想要的:

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

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

我还没有在这里看到一个不使用extglobfind,或grep的技巧是将两个文件列表作为集合,并使用comm对它们进行" diff ":

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

commdiff更可取,因为它没有多余的碎屑。

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

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

可能有用。