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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/216995/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:45:45  来源:igfitidea点击:

How can I use inverse or negative wildcards when pattern matching in a unix/linux shell?

bashshellpattern-matchingglob

提问by user4812

Say I want to copy the contents of a directory excluding files and folders whose names contain the word 'Music'.

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

cp [exclude-matches] *Music* /target_directory

What should go in place of [exclude-matches] to accomplish this?

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

回答by Vinko Vrsalovic

In Bash you can do it by enabling the extgloboption, like this (replace lswith cpand add the target directory, of course)

在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

You can later disable extglob with

您可以稍后禁用 extglob

shopt -u extglob

回答by tzot

The extglobshell option gives you more powerful pattern matching in the command line.

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

You turn it on with shopt -s extglob, and turn it off with shopt -u extglob.

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

In your example, you would initially do:

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

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

The full available extended globbing operators are (excerpt from man bash):

完整可用的ext结束globbing 运算符是(摘自man bash):

If the extglob shell option is enabled using the shopt builtin, several extended pattern matching operators are recognized.A pattern-list is a list of one or more patterns separated by a |.Composite patterns may be formed using one or more of the following sub-patterns:

  • ?(pattern-list)
    Matches zero or one occurrence of the given patterns
  • *(pattern-list)
    Matches zero or more occurrences of the given patterns
  • +(pattern-list)
    Matches one or more occurrences of the given patterns
  • @(pattern-list)
    Matches one of the given patterns
  • !(pattern-list)
    Matches anything except one of the given patterns

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

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

So, for example, if you wanted to list all the files in the current directory that are not .cor .hfiles, you would do:

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

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

Of course, normal shell globing works, so the last example could also be written as:

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

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

回答by ejgottl

Not in bash (that I know of), but:

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

cp `ls | grep -v Music` /target_directory

I know this is not exactly what you were looking for, but it will solve your example.

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

回答by Steve

If you want to avoid the mem cost of using the exec command, I believe you can do better with xargs. I think the following is a more efficient alternative to

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

回答by mivk

In bash, an alternative to shopt -s extglobis the GLOBIGNOREvariable. It's not really better, but I find it easier to remember.

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

An example that may be what the original poster wanted:

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

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

When done, unset GLOBIGNOREto be able to rm *techno*in the source directory.

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

回答by James M. Lay

A trick I haven't seen on here yet that doesn't use extglob, find, or grepis to treat two file lists as sets and "diff"them using comm:

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

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

commis preferable over diffbecause it doesn't have extra cruft.

comm更可取,diff因为它没有多余的 cruft。

This returns all elements of set 1, ls, that are notalso in set 2, ls *Music*. This requires both sets to be in sorted order to work properly. No problem for lsand glob expansion, but if you're using something like find, be sure to invoke sort.

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

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

Potentially useful.

可能有用。

回答by Abid H. Mujtaba

My personal preference is to use grep and the while command. This allows one to write powerful yet readable scripts ensuring that you end up doing exactly what you want. Plus by using an echo command you can perform a dry run before carrying out the actual operation. For example:

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

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

will print out the files that you will end up copying. If the list is correct the next step is to simply replace the echo command with the copy command as follows:

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

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

回答by mipadi

You can also use a pretty simple forloop:

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

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

回答by Daniel Bungert

One solution for this can be found with find.

可以使用 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 has quite a few options, you can get pretty specific on what you include and exclude.

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

Edit: Adam in the comments noted that this is recursive. find options mindepth and maxdepth can be useful in controlling this.

编辑:亚当在评论中指出这是递归的。find 选项 mindepth 和 maxdepth 在控制这个方面很有用。

回答by zrajm

The following works lists all *.txtfiles in the current dir, except those that begin with a number.

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

This works in bash, dash, zshand all other POSIX compatible shells.

这部作品在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. In line one the pattern /some/dir/*.txtwill cause the forloop to iterate over all files in /some/dirwhose name end with .txt.

  2. In line two a case statement is used to weed out undesired files. – The ${FILE##*/}expression strips off any leading dir name component from the filename (here /some/dir/) so that patters can match against only the basename of the file. (If you're only weeding out filenames based on suffixes, you can shorten this to $FILEinstead.)

  3. In line three, all files matching the casepattern [0-9]*) line will be skipped (the continuestatement jumps to the next iteration of the forloop). – If you want to you can do something more interesting here, e.g. like skipping all files which do not start with a letter (a–z) using [!a-z]*, or you could use multiple patterns to skip several kinds of filenames e.g. [0-9]*|*.bakto skip files both .bakfiles, and files which does not start with a number.

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