在UNIX / Linux Shell中进行模式匹配时,如何使用反或负通配符?


325

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

cp [exclude-matches] *Music* /target_directory

要实现此目标,应该用什么代替[排除匹配]?

Answers:


375

在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

14
我喜欢这个功能:ls /dir/*/!(base*)
Erick Robertson

6
您如何包含所有内容(),又如何排除!(b)?
伊利亚·林恩

4
你将如何搭配,比方说,一切开始f,除foo
Noldorin

8
为什么默认情况下禁用此功能?
weberc2

3
shopt -o -u histexpand,如果您需要查找带有感叹号的文件-默认情况下处于启用状态,extglob默认情况下处于关闭状态,以免干扰histexpand,在文档中说明了为什么这样做。匹配除foo之外的所有以f开头的内容:f!(oo),当然'food'仍然会匹配(您需要f!(oo *)才能停止以'foo'开头的内容,或者如果您想摆脱它,以'.foo'结尾的某些内容使用!(. foo)或前缀:myprefix!(.foo)(与myprefixBLAH匹配,但与myprefixBLAH.foo不匹配)
osirisgothra 2014年

227

extglob外壳选项可以让你在命令行更强大的模式匹配。

您可以使用打开它shopt -s extglob,然后使用关闭它shopt -u extglob

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

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

全部可用分机结束水珠兵运营商(摘自man bash):

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

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

因此,例如,如果要列出当前目录中不是.c.h文件的所有文件,则可以执行以下操作:

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

当然,正常的外壳球形化是可行的,因此最后一个示例也可以写成:

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

1
-d的原因是什么?
Big McLargeHuge 2015年

2
@Koveras的是,其中一个的情况下.c.h文件是目录。
tzot

@DaveKennedy列出当前目录中的所有D内容,但不列出目录中可能包含的子目录的内容D
spurra

23

不以bash(我知道)为准,但是:

cp `ls | grep -v Music` /target_directory

我知道这并不是您要找的东西,但是它将解决您的示例。


默认ls将在每行放置多个文件,这可能不会给出正确的结果。
丹尼尔·邦格特

10
仅当stdout是终端时。在管道中使用时,ls每行打印一个文件名。
亚当·罗森菲尔德

ls如果输出到终端,则仅每行放置多个文件。自己尝试-“ ls | less”每行永远不会有多个文件。
SpoonMeiser

3
它不适用于包含空格(或其他白色空格字符)的文件名。
tzot

7

如果您想避免使用exec命令的内存成本,我相信使用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/

6

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

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

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

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


5

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

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

1
这会进行递归查找,这与OP想要的行为不同。
亚当·罗森菲尔德

1
使用-maxdepth 1非递归?
avtomaton

我发现这是最干净的解决方案,而无需启用/禁用外壳程序选项。在这篇文章中,建议使用-maxdepth选项来获得OP所需的结果,但这取决于您要完成的工作。
大卫·拉波因特

如果find反引号找到任何不平凡的文件名,则会以不愉快的方式中断。
Tripleee '18

5

我个人的喜好是使用grep和while命令。这样一来,您就可以编写功能强大而又易读的脚本,以确保最终完成所需的操作。另外,通过使用echo命令,您可以在执行实际操作之前执行空运行。例如:

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

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

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

1
只要您的文件名没有任何制表符,换行符,一行中有多个空格或任何反斜杠,此方法将起作用。虽然这些是病理性病例,但最好意识到这种可能性。在中bash可以使用while IFS='' read -r filename,但是换行符仍然是一个问题。通常,最好不要使用ls枚举文件。这样的工具find更适合。
Thedward 2013年

没有任何其他工具:for file in *; do case ${file} in (*Music*) ;; (*) cp "${file}" /target_directory ; echo ;; esac; done
Thedward 2013年

mywiki.wooledge.org/ParsingLs列出了许多避免此情况的其他原因。
Tripleee '18

5

我还没有看到这里还没有使用特技extglobfind或者grep是处理两个文件列表作为集和“差异”他们使用comm

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

commdiff它更可取,因为它没有多余的东西。

此返回组1中的所有元素,ls即是也在集2, ls *Music*。这要求两个集合都必须排序才能正常工作。ls和glob扩展没问题,但是如果您使用find,请确保调用sort

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

可能有用。


1
排除的好处之一是不必首先遍历目录。此解决方案遍历了两个子目录-一个带有排除项,另一个没有。
Mark Stosberg '17

很好,@ MarkStosberg。虽然,此技术的附带好处是您可以阅读实际文件中的排除内容,例如comm -23 <(ls) exclude_these.list
James M. Lay

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在控制此方面可能很有用。


这会进行递归复制,这是不同的行为。它还为每个文件产生了一个新的过程,这对于大量文件可能效率很低。
亚当·罗森菲尔德

与复制每个文件所生成的所有IO相比,产生一个进程的成本大约为零。所以我说这对于偶尔使用已经足够了。
dland

一些解决方法的过程中产卵:stackoverflow.com/questions/186099/...
Vinko Vrsalovic

使用“ -maxdepth 1”来避免递归。
ejgottl

使用反引号获得外壳通配符扩展的模拟:cp find -maxdepth 1 -not -name '*Music*'/ target_directory
ejgottl

2

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

这部作品在bashdashzsh和所有其他POSIX兼容的炮弹。

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文件以及不以数字开头的文件。


h!有一个错误(我匹配*.txt而不是*)。立即修复。
zrajm '16

0

这样做会排除确切的“音乐”

cp -a ^'Music' /target

这个和那个排除音乐之类的东西?*或*?音乐

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

cp在MacOS手册页中有一个-a选项,但它确实完全不同的东西。哪个平台支持此功能?
Tripleee '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.