2015-02-24 81 views
3

我试图运行find,并排除了数组中列出的几个目录。我发现时,它的扩大,虽然,这是造成我的问题有些怪异的行为:在bash中扩展星号

~/tmp> skipDirs=("./dirB" "./dirC") 
~/tmp> bars=$(find . -name "bar*" -not \(-path "${skipDirs[0]}/*" $(printf -- '-o -path "%s/\*" ' "${skipDirs[@]:1}") \) -prune); echo $bars 
./dirC/bar.txt ./dirA/bar.txt 

这并没有跳过dirC我尔德预料。问题在于印刷品在"./dirC"附近展开报价。

~/tmp> set -x 
+ set -x 
~/tmp> bars=$(find . -name "bar*" -not \(-path "${skipDirs[0]}/*" $(printf -- '-o -path "%s/*" ' "${skipDirs[@]:1}") \) -prune); echo $bars 
+++ printf -- '-o -path "%s/*" ' ./dirC 
++ find . -name 'bar*' -not '(' -path './dirB/*' -o -path '"./dirC/*"' ')' -prune 
+ bars='./dirC/bar.txt 
./dirA/bar.txt' 
+ echo ./dirC/bar.txt ./dirA/bar.txt 
./dirC/bar.txt ./dirA/bar.txt 

如果我尝试删除在$(print..)的报价,那么*被立即展开,这也给了错误的结果。最后,如果我删除引号并尝试转义*,那么\转义字符将作为文件名的一部分包含在查找中,但这也不起作用。我想知道为什么上述不起作用,什么工作?如果可能,我尽量避免使用eval,但目前我没有看到解决方法。

注意:这非常类似于:Finding directories with find in bash using a exclude list,但是,该问题的发布解决方案似乎有我上面列出的问题。

+0

同样的问题的细微变化是http://stackoverflow.com/q/28682013/45375 – mklement0 2015-02-24 22:15:11

回答

5

的安全方法是明确地建立你的数组:

#!/bin/bash 

skipdirs=("./dirB" "./dirC") 

skipdirs_args=(-false) 
for i in "${skipdirs[@]}"; do 
    args+=(-o -type d -path "$i") 
done 

find . \! \(\("${skipdirs_args[@]}" \) -prune \) -name 'bar*' 

我稍微修改逻辑在你的发现,因为你必须在有轻微的(逻辑)错误:你的命令是:

find -name 'bar*' -not stuff_to_prune_the_dirs 

find如何继续?它将解析文件树,并且当它找到与bar*匹配的文件(或目录)时,它将应用-not ...部分。那真的不是你想要的!你的-prune永远不会被应用!

看看这个:

find . \! \(-type d -path './dirA' -prune \) 

这里find将彻底修剪目录./dirA和打印一切。现在它是你想要应用过滤器-name 'bar*'的一切!顺序非常重要!有这样一个很大的区别:

find . -name 'bar*' \! \(-type d -path './dirA' -prune \) 

和这样的:作为所有预期

find . \! \(-type d -path './dirA' -prune \) -name 'bar*' 

第一个不工作!第二个很好。

注意事项。

  • 我使用\!代替-not作为\!是POSIX,-not不是POSIX指定的延伸。你会认为-path不是POSIX,因此使用-not并不重要。这是一个细节,使用你喜欢的任何东西。
  • 你必须使用一些肮脏的技巧来建立你的命令来跳过你的目录,因为你必须考虑第一个术语与其他术语分开。通过使用-false初始化数组,我不必专门考虑任何条款。
  • 我在指定-type d,所以我确定我正在修剪目录。
  • 由于我的修剪确实适用于目录,因此我不必在通配符中包含通配符。这很有趣:如上所述,当您正确使用find时,您的问题似乎与通配符无法处理完全消失有关。
  • 当然,我给出的方法也非常适用于通配符。例如,如果要排除/修剪称为baz内部子目录称为foo所有子目录,鉴于skipdirs阵列由

    skipdirs=("./*/foo/baz" "./*/foo/*/baz") 
    

    将正常工作!

4

这里的问题是您在"%s/*"上使用的引号不符合您的想法。

也就是说,您认为您需要"%s/*"上的引号来阻止printf的结果被循环播放,但这不是发生的情况。尝试没有目录分隔符和文件开头和双引号结尾的相同的东西,你会明白我的意思。

$ ls 
"dirCfoo" 
$ skipDirs=("dirB" "dirC") 
$ printf '%s\n' -- -path "${skipDirs[0]}*" $(printf -- '-o -path "%s*" ' "${skipDirs[@]:1}") 
-path 
dirB* 
-o 
-path 
"dirCfoo" 
$ rm '"dirCfoo"' 
$ printf -- '%s\n' -path "${skipDirs[0]}*" $(printf -- '-o -path "%s*" ' "${skipDirs[@]:1}") 
-path 
dirB* 
-o 
-path 
"dirC*" 

明白我的意思?引号没有被shell特别处理。他们只是发生在你的情况不会发生。

这个问题是为什么像http://mywiki.wooledge.org/BashFAQ/050讨论什么等事情不起作用的一部分。

要做你想在这里,我相信你需要手动创建查找参数数组。

sD=(-path /dev/null) 
for dir in "${skipDirs}"; do 
    sD+=(-o -path "$dir") 
done 

然后展开 “$ {±标准差[@]}” 的find命令行(-not \("${sD[@]}" \)左右)上。

是的,我相信,这使得因为这是怎么回事阵列间接您链接到不正确的答案(虽然对方的回答可能工作(非空白等文件)。

+1

荣誉解释的报价问题。让我试一下总结:命令替换是_unquoted_,所以它的输出受到shell扩展的影响,包括globbing。双引号成为它们所包含的标记的_literal_部分 - 它们不被shell解析为双引号字符串(只有'eval'会这样做)。因此,glob_in_应用于_literal_'“*”',它将典型地不匹配任何东西(因为大多数文件名不包含在文字双引号中)。最终出错的是,文字双引号传递给'find' _作为参数_的一部分。 – mklement0 2015-02-24 22:14:30