Joh*_*ohn 4 bash parsing glob escaping
我正在尝试运行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
Run Code Online (Sandbox Code Playgroud)
这并没有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
Run Code Online (Sandbox Code Playgroud)
如果我尝试删除中的引号$(print..),则会*立即展开,这也会产生错误的结果.最后,如果我删除引号并尝试转义*,则\转义字符将作为文件名的一部分包含在查找中,这也不起作用.我想知道为什么以上不起作用,什么会起作用?我试图尽可能避免使用eval,但目前我没有看到解决方法.
注意:这非常类似于:使用排除列表在bash中查找find,但是,针对该问题的已发布解决方案似乎存在我在上面列出的问题.
安全的方法是显式构建您的数组:
#!/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*'
Run Code Online (Sandbox Code Playgroud)
我略微修改了你的查找中的逻辑,因为那里有一个轻微的(逻辑)错误:你的命令是:
find -name 'bar*' -not stuff_to_prune_the_dirs
Run Code Online (Sandbox Code Playgroud)
怎么find办?它将解析文件树,当它找到匹配的文件(或目录)时,bar*它将应用该-not ...部分.那真的不是你想要的!你-prune永远不会被应用!
看看这个:
find . \! \( -type d -path './dirA' -prune \)
Run Code Online (Sandbox Code Playgroud)
这里find将完全修剪目录./dirA并打印其他所有内容.现在,它是您想要应用过滤器的所有其他内容-name 'bar*'!订单非常重要!这之间有很大的不同:
find . -name 'bar*' \! \( -type d -path './dirA' -prune \)
Run Code Online (Sandbox Code Playgroud)
还有这个:
find . \! \( -type d -path './dirA' -prune \) -name 'bar*'
Run Code Online (Sandbox Code Playgroud)
第一个根本没有预期的工作!第二个很好.
笔记.
\!的,而不是-not因为\!是POSIX,-not是不是POSIX指定的延伸.你会说这-path不是POSIX,所以使用它并不重要-not.这是一个细节,使用你喜欢的任何东西.-false,我不必特别考虑任何术语.-type d这样我确定我正在修剪目录.find如上所述,当您正确使用时,您的问题似乎与您无法处理的通配符完全消失.当然,我给出的方法也适用于通配符.例如,如果要排除/修剪所调用的baz子目录内的所有子目录foo,则skipdirs给出的数组
skipdirs=( "./*/foo/baz" "./*/foo/*/baz" )
Run Code Online (Sandbox Code Playgroud)
会工作正常!