通配符扩展(globbing)在由引用和未引用的部分组成的字符串中

时间:2014-06-17 13:58:40

标签: bash shell glob quoting

我在shell脚本中有一行看起来像这样:

java -jar "$dir/"*.jar

,因为我只想执行该文件夹中恰好命名的jar文件。但这并不像我预期的那样有效。我收到错误消息:

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

正在采取' *'从字面上看,而不是做我想要的替代品。我该如何解决这个问题?

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

2 个答案:

答案 0 :(得分:4)

您只需要设置failglob

shopt -s failglob

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

PS:如果无法匹配任何*.jar,则会产生错误:

-bash: no match: *.jar

答案 1 :(得分:3)

说明和背景信息

OP的问题本身不是通配符 - 要使glob(模式)生效,特殊模式字符(例如*)必须< em> unquoted 即使在部分单引号或双引号的字符串中也能正常工作,正如OP在他的问题中所做的那样:

"$dir/"*.jar # OK, because `*` is unquoted

相反,问题是bash - 有点令人惊讶 - 保留模式未展开的默认行为(保留原样), if它碰巧不匹配任何 ,实际上导致一个不代表任何实际文件系统项的字符串。

  • 在目前的情况下,"$dir"碰巧扩展到了一个不包含*.jar个文件的目录,因此传递给java的结果字符串以 literal <结尾/ em> *.jar'<value of $dir>/*.jar'),由于未提及实际的.jar文件,导致问题中引用了错误。

Shell选项管理globbing(更正式地称为路径名扩展

  • set -fshopt -so noglob完全关闭了globbing ,因此通常会导致字符串被视为glob的不带引号的字符串(字符)被视为文字
  • shopt -s nullglob改变默认行为,将不匹配的glob扩展为空字符串
  • shopt -s failglob将默认行为更改为 报告错误,并在不匹配的glob 的情况下将退出代码设置为1 ,甚至不执行手头的命令 - 见下面的陷阱。
  • 还有其他与此讨论无关的与globbing相关的选项 - 要查看所有这些选项的列表,请运行{ 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结合使用时导致脚本自动中止的情况,以防全局不匹配:
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打开的情况下,如果globbing失败,bash永远不会执行手头的命令,||子句不会执行,并且整体继续执行。

虽然失败的glob会导致退出代码设置为1,但您无法区分由于不匹配的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返回匹配的文件名,或者如果没有匹配则返回 empty 字符串;由于命令替换($(...)),该输出被传递给-n,它测试字符串是否为空,以便整个[[ ... ]]条件的退出代码反映某些匹配(退出代码0)与否(退出代码1)。

请注意,命令替换中的任何命令都在子shell 中运行,这可确保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在函数调用时应用globbing ,并将globbing(路径名扩展)的结果作为参数传递。该函数然后简单地测试第一个结果参数(如果有的话)是否引用现有的项,并相应地设置退出代码(无论nullglob是否恰好在效果与否。)