【问题标题】:Bash check if file exists with double bracket test and wildcardsBash 使用双括号测试和通配符检查文件是否存在
【发布时间】:2014-07-07 16:37:00
【问题描述】:

我正在编写一个 Bash 脚本,需要检查是否存在看起来像 *.$1.*.ext 的文件.

这只是为了满足好奇心,因为我无法相信扩展测试无法确定文件是否存在。我知道我可以使用[[ `ls *.$1.*.ext` ]],但如果有多个匹配项,那将匹配。我可能可以通过管道将它发送到 wc 或其他东西,但这看起来很笨重。

有没有一种简单的方法来使用双括号来检查文件是否存在使用通配符?

编辑:我看到 [[ -f `ls -U *.$1.*.ext` ]] 有效,但我仍然希望不必调用 ls。

【问题讨论】:

  • 试过find -name ... ?
  • @LatinSuD:我知道几种方法来执行我正在寻找的那种检查。我真的只是想知道是否有办法使用 Bash 的双括号测试的内置检查之一来做到这一点。

标签: bash


【解决方案1】:

[ -f ... ][[ -f ... ]](以及其他文件测试运算符)都不是为使用 模式(也称为 glob、通配符表达式)而设计的 - 它们总是解释他们的操作数作为 literal 文件名。[1]

测试模式(glob)是否与完全匹配文件的一个简单技巧是使用帮助函数

existsExactlyOne() { [[ $# -eq 1 && -f $1 ]]; }

if existsExactlyOne *."$1".*.ext; then # ....

如果您只对是否有 任何 个匹配项感兴趣 - 即 一个或多个 - 该函数更简单:

exists() { [[ -f $1 ]]; }

如果你想避免一个函数,它会变得更棘手

警告:例如,此解决方案不区分常规文件目录(尽管可以修复。)

if [[ $(shopt -s nullglob; set -- *."$1".*.ext; echo $#) -eq 1 ]]; then # ...
  • 命令替换 ($(...)) 中的代码执行以下操作:
    • shopt -s nullglob 指示 bash 将模式扩展为 字符串,如果有 no 匹配项
    • set -- ... 将模式扩展的结果分配给运行命令替换的子 shell 的位置参数($1$2、...)。
    • echo $# 简单地回显位置参数的计数,然后对应匹配文件的计数;
  • 回显的数字(命令替换的 stdout 输出)成为 -eq 运算符的左侧,后者(在数字上)将其与 1 进行比较。

同样,如果您只是对是否有 任何 个匹配项感兴趣 - 即,一个或多个 - 只需替换 -eq -ge


[1]
正如@Etan Reisinger 在评论中指出的那样,在[ ... ](单括号语法)的情况下,shell 扩展了-f 操作符之前 的模式(普通命令-行解析规则适用)。

相比之下,不同的规则适用于 bash 的 [[ ... ]],它的解析方式不同,在这种情况下,只是将模式视为 文字(即,不t 展开它)。

无论哪种方式,它都不会(稳健且可预测地)与模式一起工作:

  • 使用[[ ... ]],它从不起作用:文件测试运算符始终将模式视为文字。
  • 使用[ ... ] 只有可以正常工作如果恰好有一个匹配项
    • 如果没有匹配:
      • 如果nullglob 为OFF(默认值),或者nullglob 为ON,则文件测试运算符将模式视为文字,则条件始终返回true,因为它被简化为-f,由于缺少操作数,它不再被解释为文件测试,而是被解释为 非空字符串(并且非空字符串的计算结果为真)。
    • 如果有 MULTIPLE 匹配:[ ... ] 命令作为一个整体中断,因为该模式随后扩展为多个单词,而文件测试运算符仅采用 一个论据。

【讨论】:

  • 这未能解决问题的“如果有多个匹配项将匹配”部分,但关于[ -f[[ -f 的陈述是一个很好的陈述(如果有点手波-y)在测试时,它们实际上都没有看到一个 glob)。
  • @EtanReisner:关于多次匹配的好点 - 没有仔细阅读;将编辑。
  • 如果有空格,您是否在 $1 周围加上引号?
  • @WilliamEverett: *."$1".*.ext 是一个 glob 可能会扩展为 多个 参数 - 正确分区为单个文件名,即使嵌入了空格(谢谢双引号 "$1" 部分)。因此,如果只有 1 个匹配项,$# 在函数内将是 1 - 无论 1 个匹配项是否嵌入了空格。
  • @WilliamEverett:不客气——关于否定的好点;但是,在您的简单示例中不需要括号:if ! existsOne *."$1".*.ext 工作正常 - 除非逻辑需要,否则无需创建带括号的 subshel​​l
【解决方案2】:

由于您的问题是 bash 标记的,因此您可以利用 bash 特定的工具,例如数组:

file=(*.ext)
[[ -f "$file" ]] && echo "yes, ${#file[@]} matching files"

这首先为每个匹配的文件名填充一个数组,然后仅测试第一项:通过名称引用数组而不指定索引地址其第一个元素。由于这仅代表一个文件,因此 -f 表现良好。

另一个好处是,如果您需要文件计数,填充的数组项的数量与匹配文件的数量相对应,因此可以轻松确定,如上面的回显输出所示。您可能会发现不需要定义额外的函数是一个优势。

【讨论】:

  • +1;如果您想收集实际匹配,这很方便。如果您只关心是否存在 is(至少一个)匹配项,您可以将其全部填充到一个条件中:[[ -n $(shopt -s nullglob; echo *.ext) ]] && echo "matches"。我仍然不完全清楚 OP 是否要确定是否有 exactly one 匹配,但以防万一:[[ ${#file[@]} -eq 1 && -f "$file" ]]
  • 如果您有多个匹配项,但第一个是损坏的符号链接,这将声称不存在匹配项。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-16
  • 2021-01-25
  • 1970-01-01
  • 2021-01-04
  • 1970-01-01
  • 2014-10-30
  • 1970-01-01
相关资源
最近更新 更多