仅当在脚本中运行时,“查找:路径必须先于表达式:”错误

仅当在脚本中运行时,“查找:路径必须先于表达式:”错误

如果我在命令行执行 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)

相关内容