【问题标题】:Wildcard expansion (globbing) in a string composed of quoted and unquoted parts由带引号和不带引号的部分组成的字符串中的通配符扩展(通配符)
【发布时间】:2014-06-17 13:58:40
【问题描述】:

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

java -jar "$dir/"*.jar

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

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

它按字面意思使用“*”字符,而不是进行我想要的替换。我该如何解决这个问题?

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

【问题讨论】:

  • 如果特定目录x中没有以.jar结尾的文件,则尝试展开x/*.jar的结果将是x/*.jar,不存在。至少bash 中的默认值似乎在大多数安装中都以这种方式工作。 bash 有许多选项(例如 failglobnullglob 和其他一些)可以根据您的需要修改默认行为。

标签: bash shell glob quoting shopt


【解决方案1】:

你只需要设置failglob:

shopt -s failglob

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

PS:当它无法匹配任何*.jar时会产生错误:

-bash: no match: *.jar

【讨论】:

  • shopt:设置和取消设置 shell 选项。
  • 我认为failglob 在这里可能更合适,因为运行java -jar 可能不是扩展失败的理想结果。虽然无论哪种方式,我猜你只是发出某种失败消息......
  • @twalberg:同意。虽然在这两种情况下都会收到错误消息(至少在这种情况下),但只有通过failglob 收到的错误消息会指示立即 问题(bash: no match: *.jar)。使用nullglob,该命令可能不会在其他情况下失败并导致不良行为。
【解决方案2】:

说明和背景资料

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
  • 有问题当需要了解错误的具体原因以及与|| &lt;command in case of failure&gt; 成语结合使用时:
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)。

请注意,命令替换中的任何命令都在 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是否有效或不是)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-02-15
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 2010-10-24
    相关资源
    最近更新 更多