我通常使用 Ubuntu LTS 服务器,据我所知,它符号链接/bin/sh
到/bin/dash
。许多其他发行版虽然符号链接/bin/sh
到/bin/bash
。
从中我了解到,如果#!/bin/sh
在顶部使用脚本,它可能不会在所有服务器上以相同的方式运行?
当人们希望在服务器之间最大限度地实现脚本的可移植性时,是否有建议使用哪种 shell 来处理脚本?
答案1
Shell 脚本的可移植性大致有四个级别(就 shebang 行而言):
最便携:使用
#!/bin/sh
shebang 并使用仅有的在中指定的基本 shell 语法POSIX 标准。这应该适用于几乎任何 POSIX/unix/linux 系统。(嗯,除了 Solaris 10 和更早版本,它们有真正的传统 Bourne shell,早于 POSIX,因此不兼容,因为/bin/sh
。)第二个最便携的:使用
#!/bin/bash
(或#!/usr/bin/env bash
) shebang 行,并坚持使用 bash v3 功能。这将适用于任何具有 bash 的系统(在预期位置)。第三个最可移植的方案:使用
#!/bin/bash
(或#!/usr/bin/env bash
) shebang 行,并使用 bash v4 功能。这将在任何具有 bash v3 的系统上失败(例如 macOS,由于许可原因必须使用它)。最不便携:使用
#!/bin/sh
shebang 并使用 POSIX shell 语法的 bash 扩展。这在任何有除 bash 之外的 /bin/sh 的系统上都会失败(例如最近的 Ubuntu 版本)。永远不要这样做;这不仅仅是一个兼容性问题,它完全是错误的。不幸的是,很多人都会犯这个错误。
我的建议是:使用前三个选项中最保守的选项,它提供了脚本所需的所有 shell 功能。为了获得最大的可移植性,请使用选项 #1,但根据我的经验,一些 bash 功能(如数组)非常有用,因此我将使用 #2。
最糟糕的做法是第 4 步,使用错误的 shebang。如果您不确定哪些功能是基本 POSIX,哪些是 bash 扩展,请坚持使用 bash shebang(即选项 2),或者使用非常基本的 shell(如 Ubuntu LTS 服务器上的 dash)彻底测试脚本。Ubuntu wiki 有一个值得警惕的 bashism 列表。
Unix 和 Linux 问题中有一些关于 shell 的历史和差异的非常好的信息“sh 兼容是什么意思?”以及 Stackoverflow 问题“sh 和 bash 之间的区别”。
另外,请注意,shell 并不是不同系统之间唯一的不同之处;如果您习惯使用 Linux,那么您就会习惯使用 GNU 命令,这些命令有很多非标准扩展,您可能在其他 unix 系统(例如 bsd、macOS)上找不到。不幸的是,这里没有简单的规则,您只需要知道您正在使用的命令的变化范围。
就可移植性而言,最讨厌的命令之一也是最基本的命令之一:echo
。任何时候,只要将它与任何选项(例如echo -n
或echo -e
)一起使用,或者在要打印的字符串中使用任何转义符(反斜杠),不同的版本都会产生不同的效果。任何时候,如果你想打印一个字符串,后面没有换行符,或者字符串中有转义符,请使用printf
(并了解它的工作原理——它比echo
它更复杂)。ps
命令是也一片混乱。
另一个需要注意的一般问题是命令选项语法的最新/GNUish 扩展:旧的(标准)命令格式是命令后跟选项(带有单个破折号,每个选项都是一个字母),后跟命令参数。最近的(通常不可移植的)变体包括长选项(通常以 引入--
),允许选项位于参数之后,并使用--
将选项与参数分开。
答案2
在./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
这里的想法是,我们找到一个比当前运行的 shell 更好的 shell,然后使用该 shell 重新执行脚本。txr_shell
设置环境变量,以便重新执行的脚本知道它是重新执行的递归实例。
(在我的脚本中,txr_shell
变量随后也被使用,目的恰好有两个:首先,它作为信息消息的一部分打印在脚本的输出中。其次,它作为变量安装SHELL
在中Makefile
,因此make
也将使用这个 shell 来执行配方。)
在有 dash 的系统上/bin/sh
,您可以看到上述逻辑将使用/bin/bash
它来查找并重新执行脚本。
在 Solaris 10 机器上,/usr/xpg4/bin/sh
如果未找到 Bash,则会启动。
序言是用保守的 shell 方言编写的,用于test
文件存在测试,并使用${@+"$@"}
扩展参数的技巧来适应一些损坏的旧 shell("$@"
如果我们在符合 POSIX 的 shell 中,这将会很简单)。
答案3
与 Perl、Python、Ruby、node.js 甚至(可以说)Tcl 等现代脚本语言相比,Bourne shell 语言的所有变体都客观上很糟糕。如果您必须做任何稍微复杂的事情,从长远来看,如果您使用上述其中一种而不是 shell 脚本,您会更开心。
Shell 语言相对于那些新语言的唯一优势是某物调用自身/bin/sh
保证存在于任何声称是 Unix 的东西上。然而,这些东西甚至可能不符合 POSIX;许多传统的专有 Unix 冻结了所实现的语言/bin/sh
和默认 PATH 中的实用程序事先的针对 Unix95 所要求的更改(是的,Unix95,二十年前,并且还在继续)。目录中可能会有一组 Unix95 工具,如果幸运的话,甚至是 POSIX.1-2001 工具不是在默认 PATH 上(例如/usr/xpg4/bin
),但它们不能保证存在。
然而,Perl 的基础是更倾向于在任意选择的 Unix 安装中都比 Bash 更容易出现。(我说的“Perl 基础”是指/usr/bin/perl
存在并且一些,可能相当老了,Perl 5 的版本,如果你幸运的话,该版本解释器附带的模块集也可用。)
所以:
如果你正在写一些必须有用的东西到处声称是 Unix 的(例如“configure”脚本),您需要使用#! /bin/sh
,并且您不需要使用任何扩展。现在我会在这种情况下编写符合 POSIX.1-2001 的 shell,但如果有人要求支持 rusty iron,我会准备修补 POSIXisms。
但如果你不是如果你编写了某个必须在任何地方都能运行的代码,那么当你想要使用 Bashism 时,你应该停下来,用更好的脚本语言重写整个代码。你未来的自己会感谢你。
(因此当是使用 Bash 扩展是否合适?首先:从不。其次:仅用于扩展 Bash 交互环境 — 例如提供智能制表符补全和花哨的提示。)