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

cp [exclude-matches] *Music* /target_directory


应该用什么代替[排除匹配项]完成此操作?

#1 楼

在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


您以后可以禁用extglob与

shopt -u extglob


评论


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

–埃里克·罗伯逊(Erick Robertson)
2012年4月18日在14:58

您如何包括所有内容(),又排除!(b)?

–伊利亚·林恩(Elijah Lynn)
2012年12月23日的1:35

例如,除了foo外,您如何匹配以f开头的所有内容?

–诺多林
13年6月21日在20:58

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

–weberc2
2014年9月29日在21:03

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

– osirisgothra
14-10-22在11:17

#2 楼

使用extglob shell选项可以在命令行中提供更强大的模式匹配。

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

在您的示例中,您可以最初会做:

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


完整可用的扩展glob运算符是(摘自man bash):


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



?(模式列表)
匹配给定的零个或一次模式

*(模式列表)
匹配零个或多个给定模式的出现

+(模式列表)
匹配一个或多个给定模式的出现

@(模式列表)
匹配给定模式之一

!(模式列表)
匹配除给定模式之一




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

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


当然,正常的外壳球化工作,因此最后一个示例也可以写为:

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


评论


-d的原因是什么?

–Big McLargeHuge
2015年9月20日下午16:32

如果.c或.h文件之一是目录,则为@Koveras。

–tzot
2015年9月21日14:25在

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

– spurra
18-3-29在13:50



#3 楼


(不是我所知道的),但是:

cp `ls | grep -v Music` /target_directory


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

评论


默认ls将在每行放置多个文件,这可能不会给出正确的结果。

–丹尼尔·邦格特(Daniel Bungert)
08-10-19在21:29

仅当stdout是终端时。在管道中使用时,ls每行打印一个文件名。

–亚当·罗森菲尔德
08-10-19在21:31

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

– SpoonMeiser
08-10-19在21:32

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

–tzot
09-10-18在15:51

#4 楼

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

的更有效的替代方法

#5 楼

在bash中,shopt -s extglob变量是GLOBIGNORE的替代方法。确实不是更好,但我觉得更容易记住。

原始发帖人可能想要的一个例子:

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


何时完成后,unset GLOBIGNORE就能在源目录中进行rm *techno*

#6 楼

我尚未在此处看到的不使用extglobfindgrep的技巧是将两个文件列表视为集合,并使用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)


可能有用。

评论


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

–马克·斯托斯伯格
17年12月1日在15:42

很好,@ MarkStosberg。虽然,这项技术的附带好处是您可以读取实际文件中的排除内容,例如通讯-23 <(ls)exclude_these.list

–詹姆斯·莱(James M. Lay)
17年5月5日在18:10

#7 楼

我个人的喜好是使用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


评论


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

–Theward
2013年3月14日15:16



没有任何其他工具:用于*中的文件;在(* Music *)中使用$ {file}进行大小写; (*)cp“ $ {file}” / target_directory;回声esac;完成

–Theward
13年3月14日在15:32

mywiki.wooledge.org/ParsingLs列出了您应避免这种情况的其他原因。

–tripleee
18年1月10日在11:20

#8 楼

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

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


评论


这会进行递归查找,这与OP想要的行为不同。

–亚当·罗森菲尔德
08-10-19在21:34

使用-maxdepth 1非递归?

–avtomaton
16年10月10日在19:41

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

– David Lapointe
17年5月8日,0:40

如果在反引号中使用find会发现任何不平凡的文件名,则会以不愉快的方式中断。

–tripleee
18年1月10日,11:21



它使用2个循环,永远不要使用。使用find时,使用-exec类似于find。 -not -name“ * Music *” -exec cp“ {}” / target / dir \;

– vitalii
20 Jul 19'7:14



#9 楼

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

评论


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

–亚当·罗森菲尔德
08-10-19在21:25

与复制每个文件所生成的所有IO相比,产生一个进程的成本大约为零。所以我说这足以应付偶尔的使用。

– dland
08-10-19在21:29

产生该程序的一些解决方法:stackoverflow.com/questions/186099/…

– Vinko Vrsalovic
08-10-19在21:34

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

– ejgottl
08-10-19在21:39

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

– ejgottl
08-10-19在21:41

#10 楼

以下工作列出了当前目录中所有*.txt文件,但以数字开头的文件除外。 >
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



第一行中的模式bash将导致dash循环遍历名称以zsh结尾的/some/dir/*.txt中的所有文件。
在第二行中使用case语句清除不需要的文件。 – for表达式从文件名(此处为/some/dir)中删除任何前导目录名称组件,以便模式仅可与文件的基本名称匹配。 (如果仅根据后缀清除文件名,则可以将其缩短为.txt。)
在第三行中,将跳过所有与${FILE##*/}模式/some/dir/匹配的文件)行($FILE语句跳至下一个case循环的迭代)。 –如果您愿意,可以在这里做一些更有趣的事情,例如例如使用[0-9]*跳过所有不以字母(a–z)开头的文件,或者您可以使用多种模式来跳过几种文件名,例如continue可以跳过for文件和非以数字开头的文件。


评论


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

–zrajm
16年7月1日在9:05

#11 楼

这样做会完全排除“音乐”

cp -a ^'Music' /target


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

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


评论


MacOS上的cp手册页具有-a选项,但功能完全不同。哪个平台支持此功能?

–tripleee
18年1月10日在11:19