我正在尝试编写一个find
脚本,该脚本稍后应该能够读取要从外部文件中排除的目录列表。虽然我可以自己完成这部分,但烦人的数组扩展使任务变得困难。首先,为了获得合适的样本目录树,需要进行一些“准备”:
$ mkdir tmp && cd tmp
$ mkdir excl1_dirx excl2_dirx excl3_dirx
$ touch excl1_dirx/dummy1.txt excl2_dirx/dummy2.txt excl3_dirx/dummy3.txt
$ mkdir excl1_diry excl2_diry excl3_diry
$ touch excl1_diry/dummy4.txt excl2_diry/dummy5.txt excl3_diry/dummy6.txt
$ touch dummy00.txt dummy01.txt
如果脚本有效,则只能显示dummy00.txt
和。dummy01.txt
#!/bin/bash
excl_d=("excl*_dirx" "excl*_diry")
find_str=" . -type f ! ( "
for ((i=0 ; i<$((${#excl_d[*]})) ; i++)); do
if [[ $i > 0 ]]; then
find_str+=" -o "
fi
find_str+=" -path \"./${excl_d[i]}/*\""
done
find_str+=" )"
# this is just for debugging
echo "[debug] value of str = find $find_str"
find $find_str
首先:为什么“ done
”之前的行如此(看似)复杂?好吧,bash
有时喜欢通过做用户意想不到的事情来惹恼他们。如果没有这些引号,它将扩张每个数组元素;例如excl*_dirx
将变成excl1 dirx excl2 dirx excl3 dirx
,这(显然)打破了-path
界限!尽管我在每个数组元素中使用了一对双引号,但这实际上是为了防止 bash 进行扩展恶作剧!
然而,最好的尚未到来:倒数第二行(当转义(
)
到时\(
\)
)将在 shell 中正常工作,但不能在独立脚本中正常工作。即使它不会抛出任何错误,但后一个实现的结果将是错误的。
我尝试过各种带有单引号和双引号的组合
find_str+=" -path \"./${excl_d[i]}/*\""
行,但我就是无法让它工作,即使它在倒数第二行显示时看起来绝对完美。似乎bash
在内部对待我的转义引号\"
与非转义引号不同。啊,引号内的引号几乎在任何地方都可以工作但由于某种原因,在使用该运算符时会被过滤掉+=
。
我不仅在寻找对此行为的解释,而且还在寻找如何使其在独立脚本中工作的解决方案。这一定是我犯的一个愚蠢的错误。 :-/
答案1
这里的问题是 是find_str
一个字符串,然后将其用作字符串列表。jw013的评论是正确的,并阅读我试图将命令放入变量中,但复杂的情况总是失败!。您没有将整个命令放入变量中,但是当您尝试将多个单词填充到字符串变量中时,问题就会出现。
在 Bourne/POSIX shell 中,这是不可避免的邪恶。但在 ksh/bash/zsh 中,有更好的方法:使用数组。
#!/bin/bash
excl_d=("excl*_dirx" "excl*_diry")
find_str=( . -type f \! \( )
for ((i=0 ; i<$((${#excl_d[*]})) ; i++)); do
if [[ $i > 0 ]]; then
find_str+=( -o )
fi
find_str+=( -path "./${excl_d[i]}/*" )
done
find_str+=( \) )
find "$find_str[@]}"
有一种更简单的方法来表达这样的过滤器。
#!/bin/bash
exclude_patterns=("excl*_dirx" "excl*_diry")
exclude_args=()
while [[ ${#exclude_patterns} -gt 0 ]]; do
exclude_args+=( -path "./${exclude_patterns[1]}/*" -prune -o )
shift exclude_patterns
done
find "${exclude_args[@]}" -type f
答案2
给你:
#!/bin/bash
excl_d=("excl*_dirx" "excl*_diry")
find_str=". "
for ((i=0 ; i<$((${#excl_d[*]})) ; i++)); do
if [[ $i > 0 ]]; then
find_str+=" -o "
fi
find_str+=" -path \"./\${excl_d[i]}\" -prune "
done
find_str+=" -o -type f -print "
# this is just for debugging
echo "[debug] value of str = find $find_str"
eval "find ${find_str}"
这里的问题是,当 bash 执行脚本中的一行时,find ${find_str}
它会只使用一个参数执行 find,即 find 中 main 函数的 argc 参数将等于 2。eval
相反,组成字符串,然后在 bash 中传递标记化。