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
How can I use inverse or negative wildcards when pattern matching in a unix/linux shell?
提问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 extglob
option, like this (replace ls
with cp
and add the target directory, of course)
在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
You can later disable extglob with
您可以稍后禁用 extglob
shopt -u extglob
回答by tzot
The extglob
shell 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 .c
or .h
files, you would do:
因此,例如,如果您想列出当前目录中所有不是.c
或.h
files 的文件,您可以这样做:
$ 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 extglob
is the GLOBIGNORE
variable. It's not really better, but I find it easier to remember.
在 bash 中,替代方法shopt -s extglob
是GLOBIGNORE
变量。它并不是真的更好,但我发现它更容易记住。
An example that may be what the original poster wanted:
一个可能是原始海报想要的例子:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
When done, unset GLOBIGNORE
to 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 grep
is to treat two file lists as sets and "diff"them using comm
:
我在这里还没有看到一个不使用extglob
,的技巧find
,或者grep
是将两个文件列表视为集合并使用以下方法“区分”它们comm
:
comm -23 <(ls) <(ls *Music*)
comm
is preferable over diff
because 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 ls
and 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 for
loop:
您还可以使用一个非常简单的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 *.txt
files in the current dir, except those that begin with a number.
以下作品列出*.txt
了当前目录中的所有文件,但以数字开头的文件除外。
This works in bash
, dash
, zsh
and all other POSIX compatible shells.
这部作品在bash
,dash
,zsh
和所有其他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
In line one the pattern
/some/dir/*.txt
will cause thefor
loop to iterate over all files in/some/dir
whose name end with.txt
.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$FILE
instead.)In line three, all files matching the
case
pattern[0-9]*
) line will be skipped (thecontinue
statement jumps to the next iteration of thefor
loop). – 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]*|*.bak
to skip files both.bak
files, and files which does not start with a number.
在第一行,模式
/some/dir/*.txt
将导致for
循环遍历/some/dir
名称以.txt
.在第二行中,case 语句用于清除不需要的文件。– 该
${FILE##*/}
表达式从文件名(此处/some/dir/
)中去除任何前导目录名称组件,以便模式可以仅与文件的基本名称匹配。(如果您只是根据后缀清除文件名,则可以将其缩短为$FILE
。)在第三行,所有匹配
case
模式[0-9]*
) 行的文件将被跳过(continue
语句跳转到for
循环的下一次迭代)。– 如果您愿意,您可以在这里做一些更有趣的事情,例如使用 跳过所有不以字母 (a–z) 开头的文件[!a-z]*
,或者您可以使用多种模式来跳过几种文件名,例如[0-9]*|*.bak
跳过两个.bak
文件, 以及不以数字开头的文件。