说明和背景资料
OP 的问题不在于 globbing 本身 - 要使 glob(模式)工作,特殊模式字符(例如 *)必须不加引号,它即使在部分单引号或双引号的字符串中也有效,正如 OP 在他的问题中正确所做的那样:
"$dir/"*.jar # OK, because `*` is unquoted
相反,问题出在bash 的问题 - 有点令人惊讶 - 离开模式的默认行为未扩展(保持原样),如果碰巧不匹配任何东西,实际上会生成一个不代表任何实际文件系统项的字符串。
- 在本例中,
"$dir" 碰巧扩展到了一个不包含 *.jar 文件的目录,因此传递给 java 的结果字符串以 literal *.jar 结尾('<value of $dir>/*.jar'),由于没有引用实际的.jar 文件,导致问题中引用了错误。
Shell 选项控制通配(更正式地称为路径名扩展):
-
set -f (shopt -so noglob) 完全关闭通配符,因此通常会导致字符串被视为 glob 的未加引号的字符串(字符)被视为文字。
-
shopt -s nullglob 将默认行为更改为将不匹配的 glob 扩展为 空字符串。
-
shopt -s failglob 将默认行为更改为报告错误并将退出代码设置为 1 在不匹配 glob 的情况下,甚至不执行手头的命令 -请参阅下面的陷阱。
- 还有其他与此讨论无关的通配相关选项 - 要查看所有选项的列表,请运行
{ shopt -o; shopt; } | fgrep glob。如需描述,请在man bash 中按他们的姓名搜索。
强大的通配符解决方案
注意:全局设置 shell 选项会影响当前 shell,这是有问题的,因为第三方代码通常会合理地假设默认值有效。因此,最好只临时更改 shell 选项(更改、执行操作、恢复)或使用子 shell ((...)) 本地化更改其效果。
shopt -s nullglob
-
有用在循环中使用
for 枚举匹配项 - 如果没有匹配项,它确保永远不会进入循环:
shopt -s nullglob # expand non-matching globs to empty string
for f in "$dir/"*.jar; do
# If the glob matched nothing, we never get here.
# !! Without `nullglob`, the loop would be entered _once_, with
# !! '<value of $dir>/*.jar'.
done
-
有问题与在没有文件名参数的情况下具有默认行为的命令的参数一起使用时,可能会导致意外行为:
shopt -s nullglob # expand non-matching globs to empty string
wc -c "$dir/"*.jar # !! If no matches, expands to just `wc -c`
如果 glob 不匹配任何内容,则只执行 wc -c,这 不会 失败,而是开始读取 stdin 输入(以交互方式运行时,这将只是等待交互输入行直到以 Ctrl-D 终止)。
shopt -s failglob
-
有用用于报告特定的错误消息,尤其是与
set -e 结合使用时会导致脚本自动中止,以防 glob 不匹配:
set -e # abort automatically in case of error
shopt -s failglob # report error if a glob matches nothing
java -jar "$dir/"*.jar # script aborts, if this glob doesn't match anything
-
有问题当需要了解错误的具体原因以及与
|| <command in case of failure> 成语结合使用时:
shopt -s failglob # report error if a glob matches nothing
# !! DOES NOT WORK AS EXPECTED.
java -jar "$dir/"*.jar || { echo 'No *.jar files found.' >&2; exit 1; }
# !! We ALWAYS get here (but exit code will be 1, if glob didn't match anything).
由于启用了failglob,如果通配失败,bash 甚至永远不会执行手头的命令,|| 子句也不执行,并且整体执行继续。
虽然失败的 glob 会导致退出代码设置为 1,但您将无法区分由于 glob 不匹配导致的失败与命令报告的失败(成功 glob 之后)。
无需更改 shell 选项的替代解决方案:
稍加努力,您就可以自行检查不匹配的全局对象:
临时:
glob="$dir/*.jar"
[[ -n $(shopt -s nullglob; echo $glob) ]] ||
{ echo 'No *.jar files found.' >&2; exit 1; }
java -jar $glob
$(shopt -s nullglob; echo $glob) 设置nullglob,然后用echo 扩展glob,这样子shell 要么返回匹配的文件名,要么返回一个空 字符串;由于命令替换 ($(...)),该输出被传递给 -n,它测试字符串是否为空,以便整个 [[ ... ]] 条件的退出代码反映 something 是否匹配(退出代码0) 与否(退出代码1)。
请注意,命令替换中的任何命令都在 subshell 中运行,这确保了shopt -s nullglob 的效果仅适用于该子shell,因此不会改变全局状态。
还要注意变量赋值glob="$dir/*.jar" 中的整个 右侧是如何用双引号引起来的,以说明在引用变量时引用与通配符有关的点稍后,而不是在定义时。稍后对$glob 的未加引号的引用 确保整个字符串被解释为一个glob。
带有一个小辅助函数:
# Define simple helper function.
exists() { [[ -e $1 ]]; }
glob="$dir/*.jar"
exists $glob || { echo 'No *.jar files found.' >&2; exit 1; }
java -jar $glob
辅助函数利用 shell 在调用函数时应用通配符,并将通配符(路径名扩展)的结果作为参数传递。该函数然后简单地测试第一个结果参数(如果有的话)是否引用了一个现有项,并相应地设置退出代码(无论nullglob是否有效或不是)。