“查找:路径必须在表达式之前:”仅在脚本中运行时出错

Thu*_*eez 0 shell scripting bash find

如果我在命令行执行我的 find 命令,它工作正常,但如果我在脚本中尝试它,我会收到此错误。我运行命令

FILTER=" | grep -v \"test\""
files=`find . -type f -regex \".*$ext\" $FILTER`
Run Code Online (Sandbox Code Playgroud)

如果我做

echo "find . -type f -regex \".*$ext\" $FILTER" 
Run Code Online (Sandbox Code Playgroud)

它输出

find . -type f -regex ".*.cpp" | grep -v "test"
Run Code Online (Sandbox Code Playgroud)

在命令行上工作正常。我怎样才能让它在脚本中工作?我也尝试转义 *,但得到相同的错误。

我也注意到

find . -type f -regex \".*$ext\"
Run Code Online (Sandbox Code Playgroud)

在我的 shell 脚本中运行时没有输出,我不知道为什么,因为就像上面一样,如果我在命令行运行它,我会得到一个 .cpp 文件列表。

fra*_*san 7

当 shell 到达“扩展”阶段时,控制操作符(如|)已经被识别。在搜索控制结构时不会再次解析扩展的结果。

当命令替换在

files=`find . -type f -regex \".*$ext\" $FILTER`
Run Code Online (Sandbox Code Playgroud)

展开后,Bash 将其解析为一个简单的命令 ( find) 后跟几个参数,其中两个需要展开。您可以打开跟踪以查看实际的扩展命令:

$ set -x
$ find . -type f -regex \".*$ext\" $FILTER
+ find . -type f -regex '".*cpp"' '|' grep -v '"test"'
Run Code Online (Sandbox Code Playgroud)

如果你将它与

$ set -x
$ find . -type f -regex ".*.cpp" | grep -v "test"
+ grep --color=auto -v test
+ find . -type f -regex '.*.cpp'
Run Code Online (Sandbox Code Playgroud)

您可以清楚地看到,在第一种情况下,|用作 的单字符参数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
Run Code Online (Sandbox Code Playgroud)

但请注意,当将变量作为脚本执行时,出于明显的安全原因,确保您可以控制变量的内容非常重要。由于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 {} +
Run Code Online (Sandbox Code Playgroud)

这利用了find的灵活性,并且还可以正确处理包含换行符的文件名 -find通常情况下,除非您使用类似的东西mapfile -d '' files < <(find ... -print0)(假设 Bash(自 4.4 版起)和 afind支持非标准的实现-print0)。您可以在为什么循环遍历 find 的输出不好的做法中阅读更多相关信息,也与管道find的输出有关。

再次注意,filter数组的元素可能会导致执行任意代码(想想filter=( -exec something_evil ';' )),因此您仍然需要确保您可以控制其内容。


ilk*_*chu 5

FILTER=" | grep -v \"test\""
Run Code Online (Sandbox Code Playgroud)

|当它们是扩展的结果时,像这样的运算符没有它们的特殊含义。报价也一样。

制作一个函数来保存过滤器,引用也更容易:

filter() {
    grep -v "test"
}

files=$(find . -type f -regex ".*$ext" | filter)
Run Code Online (Sandbox Code Playgroud)

请注意,如果您find像这样使用from 命令替换,您会将输出压缩为单个字符串,如果您的文件名带有空格,这将导致问题。


如果将过滤器放入变量的部分原因是有时没有过滤器,则可以通过重新定义函数将其替换为空过滤器:

filter() {
    cat
}
Run Code Online (Sandbox Code Playgroud)

或者只是创建两个函数,然后使用一个变量来决定调用哪个函数:

filter() {
    grep -v "test"
}
filter_null() {
    cat
}
filter_used=filter
files=$(find . -type f -regex ".*$ext" | $filter_used)
Run Code Online (Sandbox Code Playgroud)