使 Unix shell 脚本符合 POSIX 标准



我一直在研究一个shell脚本来自动化一些任务。确保 shell 脚本在大多数平台上正常运行的最佳方法是什么。例如,我一直在使用echo -n命令将一些消息打印到屏幕上,而无需尾随新行,并且-n开关在某些 ksh shell 中不起作用。有人告诉我脚本必须符合 POSIX 标准。如何确保脚本符合 POSIX 标准。有工具吗?或者是否有一个外壳只支持最低 POSIX 要求?

POSIX

第一步,它给你指示什么有效或无效以及为什么,是将shebang设置为/bin/sh并使用shellcheck站点来分析你的脚本.
例如,将此脚本粘贴到shellcheck编辑器窗口中:

#!/bin/sh
read -r a b <<<"$1"
echo $((a+b))

得到一个指示:"在 POSIX sh 中,这里字符串是未定义的"。

作为第二步,你可以使用一个尽可能与 POSIX 兼容的 shell.
一个与大多数其他简单 shell 兼容的 shell 是 dash,Debian 默认系统 shell,它是旧 BSD ash 的衍生物。

另一个与posix兼容的外壳是posh。

但是,破折号和/或豪华可能不适用于某些系统。

有lksh(带有ksh风格),目标是与遗留的(旧的)shell脚本兼容。从其手册:

lksh 是一个命令解释器,专门用于运行遗留的 shell 脚本。

但是在调用 lksh 时需要使用选项,例如-o posix-o sh

请注意,强烈建议至少使用 -o posix 选项调用 lksh

,如果不是同时使用 -o posix 选项和 -o sh,以完全享受与 POSIX 标准(这可能就是您首先使用 lksh 而不是 mksh 的原因)或遗留脚本。

您将调用lksh -o posix -o sh而不是简单的lksh

使用选项是使其他外壳与 POSIX 兼容的一种方法。像 lksh 一样,使用选项-o posix,像bash -o posix.

在 bash 中,甚至可以在脚本中打开 POSIX 选项,使用:

shopt -o posix            # also with: set -o posix

也可以建立指向bashzsh的本地链接,使两者都像旧的sh外壳一样。喜欢这个:

$ ln -s /bin/bash ./sh
$ ./sh

有很多替代方案(dash,posh,lksh,bash,zsh等)来获得一个可以用作POSIX shell的shell。

便携式

但是,即便如此,以上所有内容也不能确保"便携性"。

不幸的是,使 shell 脚本"符合 POSIX 标准"通常比让它在任何现实世界的 shell 上运行更容易。

唯一现实中明智的建议是在几个shell中测试你的脚本.
就像上面的列表:dash,posh,lksh和bash --posix。

Solaris 是一个独立的世界,可能需要针对/bin/sh 和 xpg4/sh 进行测试。

随访:

如何测试 shell 脚本的 POSIX 合规性?

使用 --posix命令行选项启动 Bash 或在 Bash 运行时执行 'set -o posix' 将导致 Bash 更符合 POSIX 标准,方法是在 Bash 默认值不同的区域中更改行为以匹配 POSIX 指定的行为。

参考

注意:

  • 这个答案补充了user8017719的伟大答案。

  • 根据问题中的要求,下面讨论了一个工具:虽然它直接检查 POSIX 合规性,但它在多个 shell中运行给定的脚本,特别是包括/bin/sh.

    • /bin/sh,系统默认的shell,不应该被假定支持POSIX规定的功能以外的任何功能,尽管在实践中它确实支持,在不同程度上,取决于具体的实现。因此,在一个平台上通过/bin/sh成功运行并不能保证脚本可以在另一个平台上工作。在广泛使用的 shell 中,dash最接近于仅具有 POSIX 功能的 shell。

    • 多个shell 中成功运行非常重要:

      • 如果您正在创作需要从各种 shell 中获取的脚本。
      • 如果您知道您的脚本只会遇到一组有限的已知预先 shell。

对于布丁在吃的证明方法,请考虑使用shall(我编写的实用程序),它允许您一次调用具有多个shell 的给定脚本或命令,并反馈脚本/命令成功执行了哪些目标 shell。

如果您安装了 Node.js,则可以使用npm install -g shall轻松安装它(如果没有,请按照上面的链接访问 GitHub 存储库以获取手动安装说明),然后按如下方式使用它:

shall scriptFile

或者,使用临时命令:

shall -c '<shell-commands>'

默认情况下,它调用sh,如果已安装,则调用dashbashzshksh,但您可以使用SHELLS环境变量定位已安装的任何一组 shell

使用 macOS 上的echo -n命令示例仅针对 shellshbash

$ SHELLS=sh,bash shall -c 'echo -n hi'
✓ sh (bash variant)                       [0.00s]
-n hi
✓ bash                                    [0.00s]
hi
OK - All 2 shells (sh, bash) report success.

在macOS上,bash(有效地)充当sh,虽然echo -nsh一起使用时没有失败,但您还可以看到,当bashsh方式运行时,-n未被识别为选项

另一个macOS示例显示,即使以sh方式运行,bash也允许某些特定于Bash的扩展,例如使用非标准[[ ... ]]条件(假设dash- 在Ubuntu系统上充当sh- 是通过Homebrew安装的):

$ SHELLS=sh,bash,dash shall -c '[[ -n nonempty ]] && echo nonempty'
✓ sh (bash variant)                       [0.00s]
nonempty
✓ bash                                    [0.00s]
nonempty
✗ dash                                    [0.01s]
dash: 1: [[: not found
FAILED - 1 shell (dash) reports failure, 2 (sh, bash) report success.

如您所见,Bash 以sh身份运行仍然被接受[[ ... ]],而dash是一个(大部分)仅限 POSIX 功能的 shell,失败了,因为 POSIX 只要求[ ... ]条件(作为test ...命令的别名)。

最新更新