我通常使用 Ubuntu LTS 服务器,从我理解的符号链接/bin/sh
到/bin/dash
. 许多其他发行版虽然符号链接/bin/sh
到/bin/bash
.
据我了解,如果脚本#!/bin/sh
在顶部使用它可能不会在所有服务器上以相同的方式运行?
当人们希望这些脚本在服务器之间具有最大的可移植性时,是否有关于使用哪个 shell 来执行脚本的建议做法?
我通常使用 Ubuntu LTS 服务器,从我理解的符号链接/bin/sh
到/bin/dash
. 许多其他发行版虽然符号链接/bin/sh
到/bin/bash
.
据我了解,如果脚本#!/bin/sh
在顶部使用它可能不会在所有服务器上以相同的方式运行?
当人们希望这些脚本在服务器之间具有最大的可移植性时,是否有关于使用哪个 shell 来执行脚本的建议做法?
shell 脚本的可移植性大致有四个级别(就 shebang 行而言):
最便携:使用
#!/bin/sh
shebang 并仅使用POSIX 标准中指定的基本 shell 语法。这应该适用于几乎任何 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 有一个很好的 bashisms 列表来提防。
在 Unix 和 Linux 问题“sh 兼容意味着什么?”中有一些关于 shell 的历史和差异的非常好的信息。和 Stackoverflow 问题“sh 和 bash 之间的区别”。
另外,请注意,shell 并不是不同系统之间唯一不同的东西。如果你习惯了 linux,那么你就习惯了 GNU 命令,这些命令有很多在其他 unix 系统(例如 bsd、macOS)上可能找不到的非标准扩展。不幸的是,这里没有简单的规则,你只需要知道你正在使用的命令的变化范围。
就可移植性而言,最讨厌的命令之一是最基本的命令之一:
echo
. 任何时候您将它与任何选项(例如echo -n
orecho -e
)或字符串中的任何转义符(反斜杠)一起使用时,不同的版本会做不同的事情。任何时候你想打印一个没有换行符的字符串,或者在字符串中使用转义符,请printf
改用(并了解它是如何工作的——它比echo
现在更复杂)。ps
命令也是一团糟。另一个需要注意的一般事情是命令选项语法的最新/GNUish 扩展:旧的(标准)命令格式是命令后跟选项(带有单个破折号,每个选项是单个字母),然后是命令参数。最近的(通常是不可移植的)变体包括长选项(通常用 引入
--
),允许选项出现在参数之后,并--
用于将选项与参数分开。在
./configure
为构建准备 TXR 语言的脚本中,为了更好的可移植性,我编写了以下序言。即使#!/bin/sh
是不符合 POSIX 的旧 Bourne Shell,脚本也会自行引导。(我在 Solaris 10 VM 上构建每个版本)。这里的想法是我们找到一个比我们正在运行的 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 中)的技巧。与 Perl、Python、Ruby、node.js 甚至(可以说)Tcl 等现代脚本语言相比,Bourne shell 语言的所有变体在客观上都非常糟糕。如果您必须做任何甚至有点复杂的事情,从长远来看,如果您使用上述之一而不是 shell 脚本,您会更快乐。
与那些较新的语言相比,shell 语言仍然具有的一个也是唯一的优势是,可以保证在任何声称是 Unix 的东西上都存在自称的东西。
/bin/sh
但是,有些东西甚至可能不符合 POSIX 标准;许多遗留的专有 Unix在 Unix95 要求的更改之前/bin/sh
冻结了默认 PATH 中实现的语言和实用程序(是的,Unix95,二十年前并且还在继续)。如果幸运的话,可能有一组 Unix95 甚至 POSIX.1-2001 的工具位于不在默认 PATH 上的目录中(例如),但不能保证它们存在。/usr/xpg4/bin
然而,与 Bash 相比,Perl 的基础知识更有可能出现在任意选择的 Unix 安装中。(我所说的“Perl 的基础知识”是指Perl 5 的某个
/usr/bin/perl
版本,并且可能是相当老的版本,如果幸运的话,该版本的解释器附带的一组模块也可用。)所以:
如果您正在编写必须在声称是 Unix 的任何地方都可以工作的东西(例如“配置”脚本),您需要使用
#! /bin/sh
,并且您不需要使用任何扩展。现在我会在这种情况下编写符合 POSIX.1-2001 的 shell,但如果有人要求支持生锈的铁,我会准备修补 POSIXisms。但是,如果您不是在编写必须在任何地方都可以使用的东西,那么当您完全想使用任何 Bashism 时,您应该停下来用更好的脚本语言重写整个东西。你未来的自己会感谢你。
(那么什么时候适合使用 Bash 扩展?第一顺序:从不。第二顺序:仅用于扩展 Bash 交互环境——例如提供智能制表符补全和花哨的提示。)