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

cherouvim 07/30/2017. 3 answers, 2.886 views
linux bash shell sh

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

据我所知,如果一个脚本使用#!/bin/sh ,它可能在所有服务器上运行的方式不同。

当有人希望在服务器之间实现这些脚本的最大可移植性时,有没有一种建议的做法可以将shell用于脚本?

1 Comments
Thorbjørn Ravn Andersen 08/01/2017
各种炮弹之间有细微的差异。 如果可移植性对您来说是最重要的,那么使用#!/bin/sh并且不要使用原始shell提供的任何内容。

3 Answers


Gordon Davisson 08/01/2017.

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

  1. 大多数可移植的:使用#!/bin/sh shebang并且only使用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/sh shebang并对POSIX shell语法使用bash扩展。 这在任何除bash之外的其他系统上都会失败(例如最近的Ubuntu版本)。 永远不要这样做; 这不仅仅是一个兼容性问题,它只是错误的。 不幸的是,这是很多人犯的错误。

我的建议是:使用前三个中最保守的提供脚本所需的所有shell功能。 为了实现最大的可移植性,请使用选项#1,但根据我的经验,某些bash功能(如阵列)足够有用,因此我将使用#2。

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

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

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

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

另一个需要注意的常见事项是命令选项语法的近期/ GNU扩展:旧(标准)命令格式是该命令后面是选项(带有单个破折号,每个选项都是单个字母),随后是命令参数。 最近(通常是不可移植的)变体包括长选项(通常用--来引入),允许选项在参数之后出现,并使用--将选项与参数分开。

5 comments
12 pabouk 07/30/2017
第四种选择只是一个错误的想法。 请不要使用它。
3 Gordon Davisson 07/30/2017
@pabouk我完全同意,所以我编辑了我的答案,使其更清晰。
1 jlliagre 07/30/2017
你的第一个陈述有点误导。 POSIX标准没有具体说明使用它导致未指定行为的外界情况。 而且,POSIX没有指定posix shell必须位于哪里,只有它的名字( sh ),所以/bin/sh不能保证是正确的路径。 然后,最便携的是不要指定任何shebang,或者将shebang适配到所使用的操作系统。
2 Michael Kjörling 07/30/2017
我最近刚刚用我的脚本陷入了#4,只是想不通它为什么不起作用; 毕竟,当我在shell中直接尝试它们时,相同的命令运行稳定,并且完全按照我希望它们执行的操作。 只要我将#!/bin/sh改为#!/bin/bash ,脚本就可以正常工作。 (为了我的辩护,这个剧本已经随着时间的推移从一个真正只需要sh-isms的人发展到一个依赖于bash式的行为。)
2 jlliagre 07/30/2017
@MichaelKjörling(真正的)Bourne shell几乎从不与Linux发行版捆绑在一起,无论如何都不符合POSIX标准。 POSIX标准shell是从ksh行为创建的,而不是Bourne。 遵循FHS的大多数Linux发行版都是将/bin/sh作为它们选择提供POSIX兼容性(通常是bashdash的shell的符号链接。

Kaz 07/31/2017.

在编写用于构建TXR语言的./configure脚本中,为了更好的可移植性,我编写了以下序言。 即使#!/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重新执行脚本。 txr_shell环境变量已设置,以便重新执行的脚本知道它是重新执行的递归实例。

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

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

在Solaris 10框中,如果未找到Bash,则将启动/usr/xpg4/bin/sh

序言是用保守的方言编写的,使用文件存在测试的测试,以及用于扩展参数的${@+"$@"}技巧,以适应某些破损的旧shell(如果我们是简单的话,它就是"$@"在符合POSIX的外壳中)。

2 comments
Charles Duffy 07/31/2017
如果使用正确的引用,则不需要x hackery,因为要求该习惯用法的情况包含现在不赞成使用-a-o组合多个测试的测试调用。
Kaz 07/31/2017
@CharlesDuffy确实; test x$whatever我在那里做什么看起来像一个洋葱在清漆。 如果我们不能相信破损的旧shell进行引用,那么最后的${@+"$@"}尝试是毫无意义的。

zwol 07/31/2017.

与现代脚本语言(如Perl,Python,Ruby,node.js,甚至可以说是Tcl)相比,Bourne shell语言的所有变体客观上都很糟糕。 如果你必须做任何事情,即使有点复杂,如果你使用上面的代码而不是shell脚本,从长远来看,你会更快乐。

shell语言仍然比这些新语言具有的唯一优点是,自称/bin/sh东西保证存在于任何声称为Unix的东西上。 但是,有些东西甚至可能不符合POSIX标准; 许多传统专有Unixes在Unix95(二十年前和数一数)要求的改变prior冻结了/bin/sh实现的语言和默认PATH中的实用程序。 如果你幸运的话,可能会有一套Unix95,甚至POSIX.1-2001工具, not默认路径下的工具(例如/usr/xpg4/bin ),但它们不能保证存在。

但是,Perl的基础知识more likely出现在任意选择的Unix安装上,而不是Bash。 (通过“Perl的基础知识”,我的意思是说/usr/bin/perl存在,并且是some可能是相当古老的Perl 5版本,如果幸运的话,那个版本的解释器附带的一组模块也是可用。)

因此:

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

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

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

Related questions

Hot questions

Language

Popular Tags