如何在unix / linux shell中进行模式匹配时使用反向或负向通配符?
说我想复制一个目录的内容,不包括名称中包含单词“音乐”的文件和文件夹。
cp [exclude-matches] *Music* /target_directory
应该用什么来代替[排除比赛]来完成呢?
在Bash中,你可以通过启用extglob选项来实现,就像这样(用csreplacels,然后添加目标目录)
~/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
extglob
shell选项为您在命令行中提供了更强大的模式匹配。
你用shopt -s extglob
打开它,用shopt -u extglob
把它关掉。
在你的例子中,你最初会做:
$ shopt -s extglob $ cp !(*Music*) /target_directory
全部可用的分机结束操作符(摘自man bash
):
如果extglob shell选项使用shopt内build启用,则可以识别多个扩展模式匹配操作符。 在以下描述中,模式列表是由|分隔的一个或多个模式的列表。 复合图案可以使用以下一个或多个子图案来形成:
- ?(模式列表)
匹配给定模式的零次或一次出现- *(模式列表)
匹配零个或多个出现的给定模式- +(模式列表)
匹配一个或多个出现的给定模式- @(模式列表)
匹配一个给定的模式- !(模式列表)
匹配任何东西,除了一个给定的模式
因此,例如,如果您想列出当前目录中不是.c
或.h
文件的所有文件,则可以这样做:
$ ls -d !(*@(.c|.h))
当然,正常的shell会起作用,所以最后一个例子也可以写成:
$ ls -d !(*.[ch])
不是在bash(我知道),但:
cp `ls | grep -v Music` /target_directory
我知道这不是你正在寻找的,但它会解决你的例子。
如果你想避免使用exec命令的成本,我相信你可以用xargs做的更好。 我认为以下是一个更有效的select
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/
你也可以使用一个非常简单的for
循环:
for f in `find . -not -name "*Music*"` do cp $f /target/dir done
我个人的偏好是使用grep和while命令。 这使得人们可以编写function强大且可读的脚本,确保您最终能够按照自己的意愿进行操作。 另外,通过使用回声命令,您可以在执行实际操作之前执行空运行。 例如:
ls | grep -v "Music" | while read filename do echo $filename done
将打印出您将最终复制的文件。 如果列表正确,则下一步是简单地使用copy命令replaceecho命令,如下所示:
ls | grep -v "Music" | while read filename do cp "$filename" /target_directory done
一个解决scheme可以findfind。
$ mkdir foo bar $ touch foo/a.txt foo/Music.txt $ find foo -type f ! -name '*Music*' -exec cp {} bar \; $ ls bar a.txt
查找有很多选项,你可以对你包含和排除的内容进行非常具体的描述。
编辑:亚当在评论中指出,这是recursion的。 find选项mindepth和maxdepth可以用来控制这个。
在bash中,替代shopt -s extglob
是GLOBIGNORE
variables 。 这不是更好,但我觉得更容易记住。
一个可能是原始海报想要的例子:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
完成后, unset GLOBIGNORE
,以便能够在源目录中rm *techno*
。
这将做到不包括确切'音乐'
cp -a ^'Music' /target
这和排除像音乐?*或*?音乐的东西
cp -a ^\*?'complete' /target cp -a ^'complete'?\* /target
我在这里还没有看到一个不使用extglob
, find
或者grep
extglob
,就是把两个文件列表当作集合,用diff来“比较”它们:
comm -23 <(ls) <(ls *Music*)
comm
比diff
更可取,因为它没有额外的东西。
这将返回集合1中的所有元素ls
,它们也不在集合2中, ls *Music*
。 这需要两个集合按照sorting顺序才能正常工作。 ls
和glob扩展没有问题,但是如果你使用类似find
东西,一定要调用sort
。
comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)
可能有用。
以下工作列出当前目录中的所有*.txt
文件,但以数字开头的文件除外。
这在bash
, dash
, zsh
和所有其他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
-
在第一行模式
/some/dir/*.txt
将导致for
循环遍历以.txt
结尾的/some/dir
的所有文件。 -
在第二行case语句是用来淘汰不需要的文件。 –
${FILE##*/}
expression式从文件名(这里是/some/dir/
)中剥离出任何前导目录名组件,这样patters只能匹配文件的基本名称。 (如果只是根据后缀清除文件名,可以将其缩短为$FILE
。) -
在第三行中,所有匹配
case
模式[0-9]*
)行的文件将被跳过(continue
语句跳转到for
循环的下一个迭代)。 – 如果你想在这里做一些更有趣的事情,例如使用[!az]*
跳过所有不以字母(a-z)开头的文件,或者你可以使用多种模式跳过几种文件名,例如[0-9]*|*.bak
跳过.bak
文件和不以数字开头的文件。