我在 shell 脚本中有一行如下所示:
java -jar "$dir/"*.jar
,因为我只想执行该文件夹中碰巧命名的 jar 文件。但这并没有像我预期的那样工作。我收到错误消息:
Error: Unable to access jarfile [folder-name]/*.jar
它从字面上采用“*”字符,而不是进行我想要的替换。我该如何解决?
编辑:它现在正在工作。我只是有错误的文件夹前缀:/对于任何想知道的人,这是正确的方法。
OP 的问题不在于 globbing 本身- 为了使 glob (模式)起作用,特殊模式字符(例如*
must be unquoted)即使在部分单引号或双引号的字符串中也有效,正如 OP 在他的问题:
"$dir/"*.jar # OK, because `*` is unquoted
相反,问题是bash
- 有点令人惊讶 -保留模式未扩展(保持原样)的默认行为,如果它碰巧不匹配任何东西,有效地导致一个不代表任何实际文件系统项目的字符串。
"$dir"
碰巧扩展到一个不包含*.jar
文件的目录,因此传递给的结果字符串以文字( )java
结尾,由于不引用实际文件,导致问题中引用了错误。 *.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
on,bash
如果 globbing 失败,甚至永远不会执行手头的命令,因此该||
子句也不会执行,并且整体执行会继续。
虽然失败的 glob 将导致退出代码设置为1
,但您将无法区分由于 glob 不匹配而导致的失败与命令报告的失败(在成功 glob 之后)。
无需更改外壳选项的替代解决方案:
稍加努力,您就可以自己检查不匹配的 glob:
临时:
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
然后用 展开 glob echo
,以便子shell返回匹配的文件名,或者如果没有匹配,则返回一个空字符串;由于命令替换($(...)
),该输出被传递给-n
,它测试字符串是否为空,以便整个[[ ... ]]
条件的退出代码反映是否匹配(退出代码)或不匹配(退出代码)。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
生效,这都会起作用)。
你只需要设置failglob
:
shopt -s failglob
避免*.jar
在给定文件夹中不匹配时显示文字。
PS:当它无法匹配任何内容时,这将产生错误*.jar
:
-bash: no match: *.jar