我已经搜索过类似问题的问题但未找到一个非常符合我情况的问题。
下面是一个非常简短的脚本,演示了我面临的问题:
#!/bin/bash
includeString="-wholename './public_html/*' -o -wholename './config/*'"
find . \( $includeString \) -type f -mtime -7 -print
基本上,我们需要在文件夹内搜索,但只能在其某些子文件夹中搜索。在我的较长脚本中,includeString是从数组构建的。对于这个演示,我保持简单。
基本上,当我运行脚本时,它找不到任何东西。没有错误,也没有命中。如果我手动运行find命令,它可以工作。如果我删除($ includeString)它也有效,但显然它并不限制我想要的文件夹。
那么为什么同一命令可以从命令行运行而不是从bash脚本运行?传递$ includeString会导致它失败的原因是什么?
答案 0 :(得分:2)
您遇到了shell如何处理变量扩展的问题。在你的脚本中:
includeString="-wholename './public_html/*' -o -wholename './config/*'"
find . \( $includeString \) -type f -mtime -7 -print
这会导致find
查找-wholename
与文字字符串 './public_html/*'
匹配的文件。也就是说, filename 包含单引号。由于您的路径中没有任何空格,因此最简单的解决方案是删除单引号:
includeString="-wholename ./public_html/* -o -wholename ./config/*"
find . \( $includeString \) -type f -mtime -7 -print
不幸的是,你可能会被这里的通配符扩展所困扰(shell会在find
看到它们之前尝试扩展通配符。)
但正如Etan在他的评论中指出的那样,这似乎是不必要的复杂;你可以这样做:
find ./public_html ./config -type f -mtime -7 -print
答案 1 :(得分:1)
如果你想存储一个参数列表并在以后扩展它,那么使用的正确形式是一个数组,而不是一个字符串:
includeArgs=( -wholename './public_html/*' -o -wholename './config/*' )
find . '(' "${includeArgs[@]}" ')' -type f -mtime -7 -print
BashFAQ #50中详细介绍了这一点。
答案 2 :(得分:1)
注意:正如Etan在评论中指出的那样,这个案例中更好的解决方案可能是重新构造find
命令,但是通过变量传递多个参数这是一项值得探索的技术。
<强> TL;博士强>:
问题并非特定于find
,而是 shell 如何解析命令行。
嵌入在变量值中的引号字符 视为文字 :它们既不被识别为参数边界分隔符解析后 多个参数只需直接使用它作为命令的一部分。
要强力传递存储在变量中的多个参数,
bash
,ksh
,zsh
) - 请参阅下文。xargs
- 请参阅下文。强大的解决方案:
注意:解决方案假设存在以下脚本,我们将其称为echoArgs
,它以诊断形式打印传递给它的参数:
#!/usr/bin/env bash
for arg; do # loop over all arguments
echo "[$arg]" # print each argument enclosed in [] so as to see its boundaries
done
此外,假设要执行以下命令的等效项:
echoArgs one 'two three' '*' last # note the *literal* '*' - no globbing
所有参数但变量 传递的最后一次。
因此,预期结果是:
[one]
[two three]
[*]
[last]
bash
,ksh
,zsh
): # Assign the arguments to *individual elements* of *array* args.
# The resulting array looks like this: [0]="one" [1]="two three" [2]="*"
args=( one 'two three' '*' )
# Safely pass these arguments - note the need to *double-quote* the array reference:
echoArgs "${args[@]}" last
xargs
- 符合 POSIX标准的替代方案: POSIX实用程序xargs
,与shell本身不同, 能够识别嵌入字符串中的引用字符串:
# Store the arguments as *single string* with *embedded quoting*.
args="one 'two three' '*'"
# Let *xargs* parse the embedded quoted strings correctly.
# Note the need to double-quote $args.
echo "$args" | xargs -J {} echoArgs {} last
请注意,{}
是一个自由选择的占位符,允许您控制xargs
提供的参数在结果命令行中的位置。
如果所有xarg
- 提供的参数都是 last ,则根本不需要使用-J
。
为了完整起见:eval
也可用于解析嵌入在另一个字符串中的引用字符串,但eval
存在安全风险:任意命令最终可能会被执行;鉴于上面讨论的安全解决方案,无需使用eval
。
最后,Charles Duffy在评论中提到了另一个安全的替代方案,然而,这需要更多编码:封装命令以在 shell函数中调用,将变量参数作为单独的参数传递到函数,然后操作函数内的所有参数数组$@
以补充固定参数(使用set
),并使用"$@"
调用命令。
解释shell所涉及的字符串处理问题:
当您将字符串分配给变量 时,嵌入式引号字符将成为字符串的一部分 :
var='one "two three" *'
$var
现在字面包含one "two three" *
,即以下 4 - 而不是预期的3字,由...分隔每个空间:
one
"two
- "
是字本身的一部分!three"
- "
是字本身的一部分!*
当您使用$var
不加引号作为参数列表的一部分时,上面分解为4个单词正是shell最初所做的 - 一个名为 分词 的过程。 请注意,如果您要双引号变量引用("$var"
),则整个字符串将始终成为单个参数。
$var
已扩展到其值,因此其中一个所谓的parameter expansions, shell不会尝试将该值中的嵌入引号识别为标记参数边界 - 这仅适用于指定字面的引号字符,作为命令行的直接部分(假设这些引号字符不是自己的引述)。但是,shell 还会将路径名扩展(globbing)应用于生成的4个单词,因此恰好匹配文件名的任何单词都将扩展为匹配的文件名。
简而言之: $var
&#39}中的引号字符既不会被识别为参数 - 边界分隔符也不会被删除< / em>解析后。此外,$var
的值中的字词受路径名扩展的约束。
这意味着传递多个参数的唯一方法是将它们不带引号留在变量值中(并将引用保留到该变量未加引号),其中: