2

我在 shell 脚本中有一行如下所示:

java -jar "$dir/"*.jar

,因为我只想执行该文件夹中碰巧命名的 jar 文件。但这并没有像我预期的那样工作。我收到错误消息:

Error: Unable to access jarfile [folder-name]/*.jar

它从字面上采用“*”字符,而不是进行我想要的替换。我该如何解决?

编辑:它现在正在工作。我只是有错误的文件夹前缀:/对于任何想知道的人,这是正确的方法。

4

2 回答 2

5

解释和背景信息

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

强大的 globbing 解决方案

注意:全局设置 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).

由于使用failglobon,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,它测试字符串是否为空,以便整个[[ ... ]]条件的退出代码反映是否匹配(退出代码)或不匹配(退出代码)。01

请注意,命令替换中的任何命令都在subshel​​l中运行,这确保 的效果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生效,这都会起作用)。

于 2014-06-21T01:42:27.857 回答
5

你只需要设置failglob

shopt -s failglob

避免*.jar在给定文件夹中不匹配时显示文字。

PS:当它无法匹配任何内容时,这将产生错误*.jar

-bash: no match: *.jar
于 2014-06-17T14:03:58.280 回答