bash discard/edit 部分 glob(找不到部分 glob 时脚本失败)

bash discard/edit part of glob (script fails when part of glob not found)

我正在 BASH 中编写一个脚本,它基本上就像一个简化的 gmake 和包生成器。它只支持一种语言;这是设计使然。我不打算发布此脚本,因为它仅供本地使用。

我基本上都能正常工作。在更复杂的项目中,它处理得很好。做一个简单的测试程序时出现问题

该脚本使用基本参数将 «args» 构建为合理的默认值。无论 "mode",脚本始终执行具有以下形式的行:

«compiler» «args» *.«ext»

鉴于支持的语言 (Ada) 有两个文件扩展名,以下 glob 在编译时用作 «ext»:

*.{ads,adb}

当两个扩展都存在时,正如在大型项目中所期望的那样,一切都很好。但是当 $PWD 中只存在一个扩展时,脚本失败,并且不会进行编译。

我对此感到很困惑,因为 "clean mode" 存在,它只是:

rm *.{ali,o,so}

即使 $PWD 中不存在某些列出的扩展名,这仍然有效。

我可以调用编译器两次,首先传递一个扩展名(如果存在该扩展名的文件),然后同样传递另一个扩展名。然而,这会导致许多文件的编译发生两次,这是一个明显的低效率。

我认为可以在 $PWD 中存在哪些扩展的脚本中尽早构建一个 glob,然后将该 glob 传递给编译器。但是,我完全不知道如何做到这一点。一些尝试 bash 抱怨找不到命令“*.{adb,ads}”。这真的让我大吃一惊,因为我基本上只是在做:

«compiler» «args» $FoundFiles

使用nullglob:

shopt -s nullglob
compiler other_args *.{ads,adb}

说明

让我们使用一个包含一个 .ads 文件但没有 .adb 文件的目录:

$ echo *.{ads,adb}
1.ads *.adb

默认情况下,如果没有文件与 glob 匹配,bash 将 return glob 本身,在本例中为 *.adbnullglob 选项改变了这一点,没有匹配项的 glob 将被忽略。因此:

$ shopt -s nullglob
$ echo *.{ads,adb}
1.ads

要将 nullglob 重置为关闭,请使用:

shopt -u nullglob

大括号扩展将 *.{adb,ads} 变成两个单词,*.adb*.ads。然后,其中的每一个都(独立地)全局扩展。如果 glob 扩展失败,则 glob 将不加修改地传递,而不是被删除。例如,如果您没有扩展名为 ads 的文件,您可能最终会尝试编译 f1.adb f2.adb *.ads。由于最后一个不存在,编译器会报错。

rm *.{ali,o,so}rm 会发生完全相同的事情,如果其中一个模式未经修改就通过了,则 rm 会抱怨并停止。但是如果命令真的是 rm -f *.{ali,o,so},那么它将执行得很好,因为 -f 标志告诉 rm 忽略丢失的文件(除其他外)。

您可以通过设置 nullglob shell 选项 (shopt -s nullglob) 修改 bash 对不匹配 glob 的处理;设置该选项后,不匹配的 glob 将从单词列表中删除,而不是未经修改地传递。

但是,在这种特殊情况下,您也可以使用

compile *.ad[bs]

这是一个单一的 glob,与大括号扩展的 glob 对具有相同的匹配项。如果它不匹配任何内容,这也将不加修改地通过,但这意味着根本没有 Ada 文件,这应该会触发一条错误消息。设置 nullglob 后,最终结果将是不带参数调用编译器;这也可能会产生一条错误消息,但是如果某些实用程序没有提供任何参数,它们将处理标准输入,在这种情况下您必须小心 nullglob