如果我在命令行执行 find 命令,它工作正常,但如果我在脚本中尝试它,我会收到此错误。我运行命令如下
FILTER=" | grep -v \"test\""
files=`find . -type f -regex \".*$ext\" $FILTER`
如果我做
echo "find . -type f -regex \".*$ext\" $FILTER"
它输出
find . -type f -regex ".*.cpp" | grep -v "test"
在命令行下工作得很好。我怎样才能让它在脚本中工作?我也尝试转义*,但得到了同样的错误。
我也注意到了
find . -type f -regex \".*$ext\"
在我的 shell 脚本中运行时没有输出,我不确定为什么,因为就像上面一样,如果我在命令行运行它,我会得到 .cpp 文件的列表。
答案1
当外壳到达“扩张”阶段,控制运算符(例如|
)已经被识别。在搜索控制结构时不会再次解析扩展的结果。
当命令替换为
files=`find . -type f -regex \".*$ext\" $FILTER`
被扩展后,Bash 将其解析为一个简单的命令 ( find
),后跟几个参数,其中两个需要扩展。您可以打开跟踪来查看实际的扩展命令:
$ set -x
$ find . -type f -regex \".*$ext\" $FILTER
+ find . -type f -regex '".*cpp"' '|' grep -v '"test"'
如果你把它与
$ set -x
$ find . -type f -regex ".*.cpp" | grep -v "test"
+ grep --color=auto -v test
+ find . -type f -regex '.*.cpp'
您可以清楚地看到,在第一种情况下,|
被用作 的单字符参数find
。
要动态构建和执行命令字符串,您需要显式添加新的解析阶段。eval
是一种方法:
$ set -x
$ files=$(eval "find . -type f -regex \".*$ext\" $FILTER")
++ eval 'find . -type f -regex ".*cpp" | grep -v "test"'
+++ find . -type f -regex '.*cpp'
+++ grep --color=auto -v test
但请注意,当将变量作为脚本执行时,出于明显的安全原因,确保您可以控制变量的内容非常重要。由于eval
这往往也会使程序更难以阅读和调试,因此建议仅将其用作最后的手段。
对于您的情况,更好的方法可能是:
filter=( -regex ".*$ext" '!' -name "*test*" )
find . -type f "${filter[@]}" -exec bash -c '
# The part of your script that works with "files" goes here
# "$@" holds a batch of file names
' mybash {} +
它利用了find
的灵活性,并且还可以正确处理包含换行符的文件名 -find
一般来说,这是一种极端情况,使得将 的输出保存到变量中不可靠,除非您使用类似的东西mapfile -d '' files < <(find ... -print0)
(假设 Bash (自版本 4.4 起)和find
支持非标准的实现-print0
)。您可以阅读更多相关内容为什么循环查找的输出是不好的做法?find
,也与管道的输出相关。
再次注意,filter
数组的元素可能会导致执行任意代码(考虑一下filter=( -exec something_evil ';' )
),因此您仍然需要确保可以控制其内容。
答案2
FILTER=" | grep -v \"test\""
当运算符|
是扩展的结果时,它们没有特殊含义。报价也一样。
制作一个函数来保存过滤器,引用也更容易:
filter() {
grep -v "test"
}
files=$(find . -type f -regex ".*$ext" | filter)
请注意,如果您使用find
这样的命令替换,则会将输出压缩为单个字符串,如果您的文件名带有空格,这将导致问题。
如果您将过滤器放入变量的部分原因是有时不是有了过滤器,您可以通过重新定义函数将其替换为空过滤器:
filter() {
cat
}
或者只是创建两个函数,然后使用变量来决定调用哪一个:
filter() {
grep -v "test"
}
filter_null() {
cat
}
filter_used=filter
files=$(find . -type f -regex ".*$ext" | $filter_used)