Bash检查文件是否存在双括号测试和通配符

时间:2014-07-07 16:37:00

标签: bash

我正在编写一个Bash脚本,需要检查一个文件是否存在看起来像*.$1.*.ext我可以通过POSIX测试很容易地做到这一点,因为[ -f *.$1.*.ext ]返回true,但使用双括号[[ -f *.$1.*.ext ]]失败了。

这只是为了满足好奇心,因为我无法相信扩展测试无法判断文件是否存在。我知道我可以使用[[ `ls *.$1.*.ext` ]]但如果有多个匹配则会匹配。我可以把它传递给wc或其他东西,但这看起来很笨拙。

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

编辑:我看到[[ -f `ls -U *.$1.*.ext` ]]有效,但我仍然不想打电话给她。

2 个答案:

答案 0 :(得分:9)

[ -f ... ][[ -f ... ]](以及其他文件测试运算符)都不适用于模式(又名globs,通配符表达式) - 他们总是将操作数解释为文字文件名。[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将模式扩展为字符串,如果有匹配
    • set -- ...将模式扩展的结果分配给运行命令替换的子shell的位置参数($1$2,...)。
    • echo $#简单地回应位置参数的计数,然后对应于匹配文件的计数;
  • 该回显数字(命令替换&#stdout输出)成为-eq运算符的左侧,其(数字上)将其与1进行比较。

同样,如果您只对 任何匹配感兴趣 - 即一个或多个 - 只需替换{ {1}} -eq


[1]
正如@Etan Reisinger在评论中指出的那样,在-ge(单括号语法)的情况下,shell在 [ ... ]运算符之前扩展模式甚至看到它(正常的命令行解析规则适用)。

相比之下,不同的规则适用于bash的-f,它以的方式解析,在这种情况下,只需将模式视为 literal (即,不扩展它)。

无论哪种方式,它都无法使用模式(强大且可预测)工作:

  • 使用[[ ... ]] 永远不会工作:文件测试运算符始终将模式视为文字。
  • 如果恰好有一个匹配,只有[[ ... ]] 正常正常工作
    • 如果没有匹配:
      • 文件测试操作符将模式视为文字,如果[ ... ]为OFF(默认值),或者如果nullglob为ON,则条件总是返回true,因为它被缩减为nullglob,由于缺少操作数,不再被解释为文件测试,而是非空字符串(非空字符串计算结果为真))。
    • 如果存在MULTIPLE匹配:-f命令作为一个整体中断,因为模式然后扩展为多个单词,而文件测试运算符只接受一个 em> argument。

答案 1 :(得分:4)

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

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

首先为每个匹配的文件名填充一个带有一个项目的数组,然后仅测试第一个项目:按名称引用数组而不指定索引地址的第一个元素。因为它只代表一个文件,所以-f表现得很好。

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