在bash中扩展星号

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,但是,针对该问题的已发布解决方案似乎存在我在上面列出的问题.

gni*_*urf 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*'
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)

    会工作正常!