#!/ bin / sh vs#!/ bin / bash以获得最大的可移植性

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

从那我明白,如果一个脚本使用#!/bin/sh顶部它可能不会在所有服务器上运行相同的方式?

是否有一个build议的做法,当一个服务器之间需要这些脚本的最大可移植性时,哪个shell用于脚本?

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

  1. 大多数可移植的:使用#!/bin/sh shebang, 使用POSIX标准中指定的基本shell语法。 这应该适用于任何POSIX / unix / linux系统。 (好吧,除了Solaris 10和更早的版本,它具有真正的传统Bourne shell,比POSIX如此不兼容,比如/bin/sh

  2. 其次是便携式:使用#!/bin/bash (或#!/usr/bin/env bash )shebang行,并坚持bash v3function。 这将在任何有bash(在预期的位置)的系统上工作。

  3. 第三最便携:使用#!/bin/bash (或#!/usr/bin/env bash )shebang行,并使用bash v4function。 这在任何有bash v3的系统上都会失败(例如,macOS,因为许可原因必须使用它)。

  4. 最小的可移植性:使用#!/bin/sh shebang并对POSIX shell语法使用bash扩展。 这在任何有bash / bin / sh以外的系统(比如最近的Ubuntu版本)上都会失败。 千万不要这样做; 这不仅仅是一个兼容性问题,它只是错误的。 不幸的是,这是很多人的错误。

我的build议是:使用前三个中最保守的提供脚本所需的所有shellfunction。 为了实现最大的可移植性,使用选项#1,但在我的经验中,一些bashfunction(如数组)有足够的帮助,我会去#2。

你可以做的最糟糕的事情是#4,用错误的屁股。 如果您不确定基本POSIX的function是什么,哪些是bash扩展,可以使用bash shebang(即选项#2),或者使用非常基本的shell(例如Ubuntu LTS服务器上的破折号)彻底testing脚本。 Ubuntu维基有一个很好的名单bashisms要小心 。

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

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

在可移植性方面最棘手的命令之一是最基本的: echo 。 任何时候你用任何选项(例如echo -n或者echo -e ),或者打印string中的任何转义字符(反斜杠),不同的版本都会做不同的事情。 任何时候你想打印一个没有换行符的string,或者在string中使用换码符,都可以使用printf (并学习它是如何工作的 – 它比echo更复杂)。 ps命令也是一团糟 。

另一个需要注意的地方是命令选项语法的最近的/ GNU扩展:旧的(标准)命令格式是命令之后是选项(带有单个破折号,每个选项是单个字母),然后是命令参数。 最近(通常是不可移植的)变体包括长选项(通常用--来引入),允许选项在参数之后,使用--从参数中分离选项。

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

 #!/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 

这里的想法是,我们find比我们正在运行的更好的shell,然后使用该shell重新执行脚本。 txr_shell环境variables已设置,以便重新执行的脚本知道它是重新执行的recursion实例。

(在我的脚本中, txr_shellvariables也随后被使用, txr_shell有两个目的:第一,它被作为脚本输出中的信息消息的一部分打印;第二,它作为SHELLvariables安装在Makefilemake也会使用这个shell来执行食谱。)

/bin/sh破折号的系统上,可以看到上面的逻辑会find/bin/bash然后重新执行脚本。

在Solaris 10中,如果未findBash,则将启动/usr/xpg4/bin/sh

序言是用保守的方言编写的,使用文件存在testing的testing,用${@+"$@"}来展开一些破旧的shell(如果我们是"$@"在符合POSIX的shell中)。

与现代脚本语言比如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的基础知识更可能出现在任意select的Unix安装上,而不是Bash。 (通过“Perl的基础知识”,我的意思是说/usr/bin/perl存在,并且是一些 Perl 5的版本,如果幸运的话,那个版本的解释器附带的模块也是可用。)

因此:

如果你正在编写一些需要在Unix上工作的东西(比如“configure”脚本),你需要使用#! /bin/sh #! /bin/sh ,你不需要使用任何扩展名。 现在,我会在这种情况下编写符合POSIX.1-2001标准的shell,但是如果有人要求支持生锈的铁,我会准备好修补POSIXisms。

但是如果你没有写出一些必须在任何地方工作的东西,那么当你想要使用任何Bashism的时候,你应该停下来用一个更好的脚本语言来重写整个事物。 你未来的自我会感谢你。

(所以什么时候使用Bash扩展合适的?为了一阶:从不。为了二阶:只扩展Bash交互环境 – 例如提供智能的tab完成和奇特的提示。