我正在尝试运行find
,并排除数组中列出的几个目录。然而,当它扩展时,我发现了一些奇怪的行为,这导致了我的问题:
~/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 :(得分: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" )
工作正常!
答案 1 :(得分: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
然后展开" $ {sD [@]}"在find
命令行(-not \( "${sD[@]}" \)
左右)。
是的,我相信这会使你链接的答案不正确(尽管另一个答案可能有用(对于非空白等文件),因为正在进行数组间接。