我正在考虑在我正在编写的实用程序脚本中使用标注jq
,而不是手动解析 JSON。
为了使脚本尽可能地可移植,我想知道哪些版本的 Unix/Linux/MacOS/WSL/etc 支持此命令而无需安装任何额外的包。
同样,在过去,我发现一些脚本使用调出wget
,但默认情况下至少一种版本中缺少调出。
这让我开始思考:是否有某种工具可以告诉人们以这种方式在脚本中使用任意命令是否“安全”?理想情况下,它可以像caniuse 网站一样,显示哪些主要版本在其默认 shell 中支持和不支持命令。
===
编辑:感谢用户 Kusalananda 指出我的示例不是 POSIX 命令,我更改了标题以避免错误标记它们。
有一些工具或网站可用于查找某个项目的哪个版本可在各个发行版(包括非 Linux 系统)中使用;请参阅是否有工具/网站可以比较不同 Linux 发行版中的软件包状态?
很难明确说明哪些系统默认有某个软件包。在某些情况下,没有真正可靠的默认软件包;例如,大多数 Debian 系统
wget
在最初设置时都会有该软件包(因为它在那里是“标准”的),但 Debian 容器镜像默认不包含它,并且没有任何强制要求wget
在 Debian 系统上安装的东西。用户也无需使用其系统的软件包(如果有)来提供给定的二进制文件。他们可能同时拥有打包版本和手动安装版本,仅查看软件包可能会给人错误的印象(例如,如果您至少需要一个特定版本,并且已安装的软件包较旧,但用户在其路径上有合适的版本)。
所有这些意味着,您只能检查命令是否存在,如果找不到所需的所有命令,则会失败,并提供指向说明如何安装它们的说明的链接。
在没有安装包的所有版本中,几乎没有任何命令会默认出现。
您可以考虑POSIX 标准的强制性实用程序部分,但虽然其中许多实用程序存在于所有系统上(grep、basename、cut、grep、cmp……),但其他实用程序根本不常见或需要单独的包(compress、pax、处理 SCSS 文件的包……)。
使用附加程序并要求它们可用并没有错。
所需的通用脚本
jq
可以以此开头:在编写通用脚本(即不是那些我已经了解系统并且可以假设已安装某些程序的脚本)时,我有时会使用一个经验法则,即考虑那些被 Debian/Ubuntu 视为必需的软件包会存在,而其余的可能不存在。
基本软件包是一组“必须始终在系统上可用且可用的”软件包(请参阅 Debian 基本软件包政策手册)。
Debian 软件包可以使用基本程序而无需声明它们。在所有其他情况下,它们必须声明依赖关系。因此,如果将程序打包到 中
.deb
,则您测试的那些非基本实用程序(例如jq
或wget
)与必须在软件包中声明的相同,而您可以跳过 coreutils 中的那些。这个列表显然不是通用的,Debian 家族中的几个发行版之间可能存在差异(以及较新的版本,包括早期版本中不重要的程序),但我发现它已经足够好了。如果它在基本包中并且包含在 POSIX 中,那么直接使用可能是安全的。如果它不是基本包,你至少知道它在那里不可用,所以你应该检查一下。
一些注意事项:
比丢失的程序更烦人的是那些具有不同行为/缺少您可能想要使用的功能的程序。
例如:
BASH_VERSION
如果您需要检查运行脚本的 bash 版本,请使用)sed
的参数语义略有不同-i
wget
你发现的实际上是一个busybox 小程序(它缺少“正常” GNU wget 的许多功能)有依赖关系并不坏(可能甚至是正确的选择),而且你不需要支持每个系统。特别是当你没有测试系统来测试它时。你需要在某个地方划一条线,通常会有一个或几个你支持的设置,这些设置是你使用的和可能测试的,以及尽最大努力和一般期望支持其他设置。
目前没有这样的资源。我真的希望有,因为这样我的工作会简单得多。软件包可用性问题已经解决(请参阅https://repology.org/了解目前最好的资源),但默认安装的内容尚未解决。对于许多 Linux 发行版来说,即使没有安装,检查起来也并不特别困难,只需要花点功夫去追踪完整的依赖关系树(例如,对于 Arch Linux,请查看基础软件包的依赖关系,但您需要跟踪它所依赖的每个东西的依赖关系)。棘手的是,给定发行版的“基础”通常因安装目标而异(例如,许多发行版在 Docker 镜像中遗漏了一些东西),也可能因用户环境的选择而异(如果您选择安装 GUI,您显然会得到一组不同的叶依赖项)。
作为一个在工作中编写大量shell脚本的人,你可以做一些事情来帮助缓解这类问题:
command -v
查找命令。这几乎适用于您关心支持的任何地方,它可以简单地转换为条件if
(例如command -v jq >/dev/null 2>&1
:)或用于分配变量(例如:`JQ_PATH="$(command -v jq 2>/dev/null || true)"),并且它避免了尝试调用用户定义的别名的可能性(这几乎不是您想要在可移植脚本中执行的操作)。jq
都可以用 Python 的json
模块和来完成print()
。基本的 HTTP/HTTPS 交互可以通过 Python 的轻松完成urllib
。readlink
可以使用 Python 非常轻松地模拟。还有很多其他的。需要注意的是确保您能理智地处理 Python 本身的版本差异,但根据您的目标平台以及您到底需要做什么,这可能不是问题。请注意,这并不是建议您用 Python 编写脚本,而是为了允许使用来python -c
运行简短的代码片段,而不是调用等效的程序。uname
通常已经足够好了)。