#!/bin/sh 与 #!/bin/bash 的最大可移植性

che*_*vim 37 linux shell bash sh

我通常使用 Ubuntu LTS 服务器,从我理解的符号链接/bin/sh/bin/dash. 许多其他发行版虽然符号链接/bin/sh/bin/bash.

#!/bin/sh由此我了解到,如果脚本在顶部使用,它可能不会在所有服务器上以相同的方式运行?

当您希望在服务器之间最大程度地可移植这些脚本时,是否有建议将哪个 shell 用于脚本?

Gor*_*son 67

shell 脚本大约有四个级别的可移植性(就 shebang 行而言):

  1. 最便携:使用#!/bin/shshebang 并使用POSIX 标准中指定的基本 shell 语法。这应该适用于几乎所有 POSIX/unix/linux 系统。(好吧,除了 Solaris 10 及更早版本,它们具有真正的传统 Bourne shell,早于 POSIX,因此不符合,如/bin/sh.)

  2. 第二个最便携:使用#!/bin/bash(或#!/usr/bin/env bash)shebang 行,并坚持使用 bash v3 功能。这适用于任何具有 bash 的系统(在预期位置)。

  3. 第三个最便携:使用#!/bin/bash(或#!/usr/bin/env bash)shebang 行,并使用 bash v4 功能。这将在任何具有 bash v3 的系统上失败(例如 macOS,出于许可原因必须使用它)。

  4. 最不便携:使用#!/bin/shshebang 并使用 bash 对 POSIX shell 语法的扩展。这将在任何系统上失败,因为 /bin/sh 具有除 bash 之外的其他内容(例如最近的 Ubuntu 版本)。永远不要这样做;这不仅仅是一个兼容性问题,这完全是错误的。不幸的是,这是很多人都会犯的错误。

我的建议:使用提供脚本所需的所有 shell 功能的前三个中最保守的一个。为了最大的可移植性,请使用选项 #1,但根据我的经验,一些 bash 功能(如数组)非常有用,我将使用 #2。

你能做的最糟糕的事情是#4,使用错误的shebang。如果您不确定哪些功能是基本的 POSIX,哪些是 bash 扩展,要么坚持使用 bash shebang(即选项 #2),要么使用非常基本的 shell(如 Ubuntu LTS 服务器上的 dash)彻底测试脚本。Ubuntu wiki 有一个很好的 bashisms 列表需要注意

在 Unix 和 Linux 问题“与 sh 兼容是什么意思?”中,有一些关于 shell 之间的历史和差异的非常好的信息和 Stackoverflow 问题“sh 和 bash 之间的区别”

另外,请注意 shell 并不是不同系统之间唯一不同的东西;如果你习惯了 linux,你就会习惯 GNU 命令,它有很多非标准的扩展,你可能在其他 unix 系统(例如 bsd、macOS)上找不到。不幸的是,这里没有简单的规则,您只需要知道您使用的命令的变化范围。

就可移植性而言,最讨厌的命令之一是最基本的命令之一:echo. 任何时候您将它与任何选项(例如echo -necho -e)或字符串中的任何转义(反斜杠)一起使用以进行打印,不同的版本会做不同的事情。任何时候你想打印一个没有换行符的字符串,或者字符串中有转义符,请printf改用(并了解它是如何工作的——它比echo现在更复杂)。该ps命令还很乱

另一个需要注意的一般事项是最近/GNUish 对命令选项语法的扩展:旧(标准)命令格式是命令后跟选项(带有一个短划线,每个选项都是一个字母),然后是命令参数。最近的(通常是不可移植的)变体包括长选项(通常用 引入--),允许选项在参数之后,并--用于将选项与参数分开。

  • 第四个选项只是一个错误的想法。请不要使用它。 (12认同)
  • @pabouk 我完全同意,所以我编辑了我的答案以使其更清楚。 (3认同)
  • 最近我用我的一个脚本掉到了#4,只是想不通为什么它不起作用;毕竟,当我直接在 shell 中尝试这些命令时,相同的命令运行良好,并且完全符合我的要求。但是,一旦我将 `#!/bin/sh` 更改为 `#!/bin/bash`,脚本就可以完美运行。(为了我的辩护,随着时间的推移,该脚本已经从一个真正只需要 sh-isms 的脚本演变为一个依赖于类似 bash 的行为的脚本。) (2认同)
  • @MichaelKjörling(真实的)Bourne shell 几乎从未与 Linux 发行版捆绑在一起,并且无论如何都不符合 POSIX。POSIX 标准 shell 是根据 `ksh` 行为创建的,而不是 Bourne。大多数遵循 FHS 的 Linux 发行版所做的是将 `/bin/sh` 作为他们选择的 shell 的符号链接,以提供 POSIX 兼容性,通常是 `bash` 或 `dash`。 (2认同)

Kaz*_*Kaz 5

./configure为构建 TXR 语言准备的脚本中,为了更好的可移植性,我编写了以下序言。即使#!/bin/sh是不符合 POSIX 的旧 Bourne Shell,该脚本也会自行引导。(我在 Solaris 10 VM 上构建每个版本)。

#!/bin/sh

# use your own variable name instead of txr_shell;
# adjust to taste: search for your favorite shells

if test x$txr_shell = x ; then
  for shell in /bin/bash /usr/bin/bash /usr/xpg4/bin/sh ; do
    if test -x $shell ; then
       txr_shell=$shell
       break
    fi
  done
  if test x$txr_shell = x ; then
    echo "No known POSIX shell found: falling back on /bin/sh, which may not work"
    txr_shell=/bin/sh
  fi
  export txr_shell
  exec $txr_shell $0 ${@+"$@"}
fi

# rest of the script here, executing in upgraded shell
Run Code Online (Sandbox Code Playgroud)

这里的想法是我们找到一个比我们正在运行的 shell 更好的 shell,并使用该 shell 重新执行脚本。在txr_shell环境变量设置,使重新执行脚本知道它是重新执行递归实例。

(在我的脚本中,该txr_shell变量也随后被使用,正好有两个目的:首先,它作为脚本输出中信息性消息的一部分被打印出来。其次,它被安装为 中的SHELL变量Makefile,以便make将使用这个shell 也用于执行配方。)

/bin/sh破折号的系统上,您可以看到上述逻辑将找到/bin/bash并重新执行脚本。

在 Solaris 10 机器上,/usr/xpg4/bin/sh如果没有找到 Bash ,它将启动。

序言是用保守的 shell 方言编写的,test用于文件存在测试,以及${@+"$@"}扩展参数以迎合一些损坏的旧 shell的技巧("$@"如果我们在符合 POSIX 的 shell 中就简单了)。