如果Bourne shell在发行版中不可用,则在hashbang中使用/ bin / sh是否正确?


15

通常,shell脚本在脚本文件的第一行包含以下注释:#!/bin/sh。根据我所做的研究,这称为“哈希爆炸”,这是常规注释。该注释通知Unix该文件由Bourne Shell在目录下执行/bin

我的问题从这一点开始。到目前为止,我还没有看到这样的评论#!/bin/bash。一直如此#!/bin/sh。但是,Ubuntu发行版没有Bourne Shell程序。他们有Bourne Again Shell(bash)。

在这一点上,将注释#!/bin/sh放在用Ubuntu发行版编写的Shell脚本中是否正确?



1
请参阅我对以下serverfault问题的回答:#!/ bin / sh与#!/ bin / bash以获得最大的可移植性
Gordon Davisson

俗称shebang
fpmurphy

Answers:


16

#!/bin/sh应该适用于所有Unix和类Unix发行版。只要您的脚本保持与POSIX兼容,它通常被认为是最可移植的hashbang。/bin/sh假定该外壳是实现POSIX外壳标准的外壳,而不管该外壳实际伪装了什么/bin/sh外壳。


#!/bin/sh现在通常只是一个链接,因为不再维护Bourne shell。在许多Unix系统上/bin/sh是的链接/bin/ksh/bin/ash在许多基于RHEL的Linux系统上的链接/bin/bash,但是在Ubuntu和许多基于Debian的系统上的链接/bin/dash。当所有外壳程序以方式调用时sh,将进入POSIX兼容模式。

虽然hashbang是一个重要的占位符,因为它比其他方法具有更大的可移植性,只要您的脚本严格符合POSIX(重复强调重要性)即可。

注意:bash在POSIX模式下调用时,它仍将允许某些非POSIX的东西,例如[[,数组等。这些事情在非bash系统上可能会失败。


3
“在Ubuntu上,它是/ bin / dash的链接”,通常在其他基于Debian的系统中也是如此。在FreeBSD / GhostBSD上的IIRC也是一个符号链接/bin/ash
Sergiy Kolodyazhnyy

要完全便携,请在shebang和(绝对)解释程序路径之间留一个空格。一些系统寻找#! /而不是仅仅#!
西蒙·里希特

5
@SimonRichter对此的引用很高兴看到。
库萨兰达

@SergiyKolodyazhnyy在FreeBSD上sh的安装版本是ash,而不是符号链接。
罗布

2
@Kusalananda,嗯,此页面说这是一个神话,因此可能会被忽略。
西蒙·里希特

12

你已经落后了。/bin/sh这些天几乎从来都不是Bourne弹壳,而当它是¹时,当您使用#! /bin/shshe-bang 时遇到问题。

Bourne外壳是70年代后期编写的外壳,并取代了之前的Thompson外壳(也称为sh)。在80年代初期,David Korn为Bourne shell编写了一些扩展名,修复了一些错误和设计笨拙(并引入了一些),并将其称为Korn shell。

在90年代初,POSIX 根据Korn Shell的子集指定了sh 语言,大多数系统现在已将其更改/bin/sh为Korn Shell或符合该规范的Shell。在BSD的情况下,他们逐渐更改了它们的名称/bin/sh(最初(出于许可原因不再使用Bourne shell)之后),即Almquist shell,它是具有某些ksh扩展名的Bourne shell的克隆,因此符合POSIX。

如今,所有POSIX系统都有一个称为POS的外壳程序sh(通常,但不一定在/binPOSIX 中,它不指定其指定的实用程序的路径)。它通常基于ksh88,ksh93,pdksh,bash,ash或zsh²,但不是Bourne shell,因为Bourne shell从不符合POSIX³。几个这些炮弹(bashzshyash和一些pdksh衍生物时作为调用使POSIX兼容模式sh,且较少柔顺以其他方式)。

bash(对Korn外壳的GNU回答)实际上是唯一经过认证的开源外壳(并且可以说只有当前维护,因为其他外壳通常基于ksh88,自90年代以来没有任何新功能)。符合POSIX sh(作为macOS认证的一部分)。

当使用#! /bin/sh -she-bang 编写脚本时,应使用标准sh语法(如果要可移植,还应对脚本中使用的实用程序使用标准语法,解释外壳时不仅涉及外壳,脚本),则sh使用该标准语法解释器的实现(kshbash...)都没有关系。

只要您不使用这些外壳,这些外壳都可以扩展标准。就像编写C代码一样,只要您编写标准C代码并且不使用一个编译器(例如gcc)或另一个编译器的扩展名,则只要编译器兼容,无论编译器实现如何,您的代码都应编译为OK。

在这里,你的#! /bin/sh她爆炸,你的主要问题将是其中的系统/bin/sh是Bourne shell的,对于实例不支持标准功能,如$((1+1))$(cmd)${var#pattern}...在你可能需要这种情况下,像变通办法:

#! /bin/sh -
if false ^ true; then
  # in the Bourne shell, ^ is an alias for |, so false ^ true returns
  # true in the Bourne shell and the Bourne shell only.
  # Assume the system is Solaris 10 (older versions are no longer maintained)
  # You would need to adapt if you need to support some other system
  # where /bin/sh is the Bourne shell.
  # We also need to fix $PATH as the other utilities in /bin are
  # also ancient and not POSIX compatible.
  PATH=`/usr/xpg6/bin/getconf PATH`${PATH:+:}$PATH || exit
  export PATH
  exec /usr/xpg4/bin/sh "$0" "$@"
  # that should give us an environment that is mostly  compliant
  # to the Single UNIX Specification version 3 (POSIX.2004), the
  # latest supported by Solaris 10.
fi
# rest of the script

顺便说一句,默认情况下/bin/sh不是Ubuntu bash。这是dash这些天,这是基于NetBSD的外壳sh,本身基础上,Almquist外壳是主要不同之处在于它不支持多字节字符POSIX兼容。在Ubuntu和其他基于Debian的系统,你可以选择bashdash/bin/shdpkg-reconfigure dash4shDebian附带的脚本在两个Shell中的工作方式应与写入Debian策略标准(POSIX标准的超集)的方式相同。您可能会发现他们还工作确定zshsh仿真或bosh(可能不是ksh93,也不yash该没有local内置(由Debian政策而不是POSIX)要求)。

unix.stackexchange.com上范围内的所有系统在sh 某处都有POSIX 。它们中的大多数都有一个/bin/sh(您可能会发现非常稀有的没有/bin目录,但您可能并不在意),并且通常使用POSIX sh解释器(在极少数情况下,可以使用(非标准)Bourne shell) )。

但是,这sh是您可以确定在系统上找到的唯一Shell解释器可执行文件。对于其他外壳,您可以确定macOS,Cygwin和大多数GNU / Linux发行版都具有bash。SYSV派生的操作系统(Solaris,AIX ...)通常具有ksh88,可能还有ksh93。OpenBSD,MirOS将具有pdksh派生。macOS将具有zsh。但除此之外,我们将无法保证。无法保证是否bash将其他外壳程序安装在/bin其他任何外壳中(例如,通常在/usr/local/binBSD上找到)。当然,也不保证将要安装的外壳版本。

请注意,这#! /path/to/executable不是约定,它是所有类Unix内核(由Dennis Ritchie于80年代初引入)的功能,它允许通过在第一行中指定解释器的路径来执行任意文件#!。它可以是任何可执行文件。

当您执行第一行以开头的文件时#! /some/file some-optional-arg,内核最终/some/filesome-optional-arg,脚本的路径和原始参数作为参数执行。您可以在第一行#! /bin/echo test中查看发生了什么:

$ ./myscript foo
test ./myscript foo

当您使用/bin/sh -而不是时/bin/echo test,内核将执行/bin/sh - ./myscript foosh解释存储在其中的内容代码,myscript并忽略第一行作为注释(以开头#)。


¹大概是唯一系统今天的任何人都会遇到一个/bin/sh是基于Bourne shell是Solaris 10操作系统的Solaris是为数不多的Unix系统是决定继续Bourne shell中的一个出现了向后兼容性(POSIX的sh语言是不完全向后兼容Bourne Shell)(至少对于台式机和完整服务器部署而言)sh(其他位置)具有POSIX (/usr/xpg4/bin/sh基于ksh88,位于中),但在Solaris 11中有所更改,/bin/sh现在为ksh93。其他的大多数都已失效。

² 所述/bin/sh的MacOS / X曾经是zsh,但后来改变到bashzsh用作POSIX sh实现不是主要重点。其sh模式主要是能够在脚本中嵌入或调用(source)POSIX sh代码zsh

³最近,@ schily扩展了OpenSolaris Shell(基于SVR4 Shell,基于Bourne Shell)以使其符合POSIX,bosh但我尚不知道它已在任何系统上使用。随着ksh88这使得它的第二兼容POSIX的shell基于Bourne外壳的代码

4在较旧的版本中,您也可以使用mkshPOSIX或更高版本的POSIX lksh。那就是基于pdksh的MirOS(以前的MirBSD)外壳,其本身基于Forsyth外壳(Bourne外壳的另一种实现))


次要问题(关于外壳程序代码中间答案):exec sh "$0" "$@"设置后是否应该立即使用PATHsh我本以为那应该从正确的地方开始。
库萨兰达

1
@Kusalananda,它应该可以工作,但是风险更大,因为如果出现问题,我们可能会陷入无尽的循环(例如,sh权限错误,getconf修改...)。无论如何,其余部分特定于Solaris 10,因此我们还可以对所有内容(包括的内容)进行硬编码$PATH
斯蒂芬·查泽拉斯

使用ksh作为其POSIX Shell的OSX的任何引文。它的默认外壳曾经是tcsh,但我不记得没有使用bash,特别是因为Korn Shell直到OSX
发行

1
@Mark,我不是说OSX曾经使用过ksh。我确实曾经说过它是zsh,后来他们改用bash(bash的一种特殊构建)。
斯特凡Chazelas

1
@fpmurphy,如果您查看早期版本,则无论ksh与Bourne Shell不兼容的地方,bash都已经与ksh站在一起。虽然您必须等到bash2才能达到与ksh88相同的功能级别,但是bash最初已经具有多个ksh扩展,例如$(...),emacs / vi模式,代字号/大括号扩展(最初来自csh的扩展),fc,排版,别名和ksh式功能语法...
斯特凡Chazelas

8

是的,您可以#!/bin/sh在脚本中使用,因为/bin/sh(希望如此)在此类系统上提供了该脚本,通常是通过某种bash行为使链接(或多或少)像will那样进行sh。例如,这是一个Centos7系统,它链接shbash

-bash-4.2$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Dec  4 16:48 /bin/sh -> bash
-bash-4.2$ 

你也可以使用#!/bin/bash,如果你正在写一个bash只对系统脚本,并要使用bash的功能。然而,这样的脚本将便携性问题的困扰,例如在OpenBSD,其中bash仅安装如果管理员费力安装它(我没有),然后将其安装到/usr/local/bin/bash,没有/bin/bash。严格的POSIX #!/bin/sh脚本应该更具可移植性。


请注意以下sh几点:“当以方式调用时,Bash在读取启动文件后进入POSIX模式。” - gnu.org/software/bash/manual/bashref.html#Bash-POSIX-Mode
格伦·杰克曼

2
当我在工作时为RHEL服务器编写脚本时,我会#!/bin/bash精确地使用它,以便可以利用非POSIX功能。
蒙蒂·哈德

RHEL /bin/sh上的@MontyHarder IIRC 是一个符号链接bash,对吗?我当然知道在CentOS上是这样。
Sergiy Kolodyazhnyy

1
@SergiyKolodyazhnyy是的,在我刚刚检查的RHEL服务器上,它是的符号链接bash。二进制文件知道用来调用它的名称,何时调用sh它就像老式的Bourne一样sh
蒙蒂·哈德

6

你问

将注释#!/ bin / sh放在以ubuntu发行版编写的shell脚本中是否正确?

答案取决于您在Shell脚本中编写的内容。

  • 如果您严格使用兼容POSIX的可移植脚本,并且不使用任何特定于bash的命令,则可以使用/bin/sh

  • 如果您知道仅在具有bash的计算机上使用脚本,并且想使用bash特定的语法,则应该使用/bin/bash

  • 如果要确保该脚本可在各种Unix计算机上运行,则应仅使用POSIX兼容的语法,并且/bin/sh

  • 如果经常使用另外一个shell(如KSHzsh的tcsh的),并且要使用的语法在你的脚本,那么你应该用适当的解释(如/bin/ksh93/bin/zsh/bin/tcsh


3

“#!” 评论并不总是使用/bin/bash/bin/sh。它仅列出解释器应包括的内容,而不仅仅是列出shell脚本。例如,我的python脚本通常以开头#!/usr/bin/env python

现在的区别#!/bin/sh#!/bin/bash/bin/sh不是总是一个符号链接/bin/bash。通常但并非总是如此。Ubuntu在这里是一个明显的例外。我已经看到脚本在CentOS上运行良好,但是在Ubuntu上却失败了,因为作者使用了bash特定语法#!/bin/sh


1

对不起,在所有这些伟大的答案是说浇一些冷水是/bin/sh存在于所有 Unix系统中所有 -它存在,但有史以来最常用的Unix系统除外:Android。

Android的外壳程序为/system/bin/sh,并且通常无法制作/bin/sh,即使在有根的系统上,链接(由于使用selinux和features(7)边界集锁定系统的方式)。

对于那些会说android不符合POSIX的人:大多数Linux和BSD发行版都不是。而且标准没有强制/bin/sh使用这种方法:

应用程序应注意,PATH外壳程序的标准不能假定为/bin/sh/usr/bin/sh,并且应通过询问PATH返回的来确定,以getconf PATH确保返回的路径名是绝对路径名而不是内置的外壳程序。


尽管所有试图与POSIX兼容的系统都有许多一致性错误(包括已认证的错误),但是Android(以及大多数其他嵌入式系统,例如路由器或灯泡OS)并不打算与POSIX兼容。除了它的POSIX界面外,Android不在这里。
斯特凡Chazelas

@Christopher,哪个getconf?例如在Solaris 11.4上,那会是其中之一/usr/bin吗?一入/usr/xpg4/bin(符合SUSv2),一入/usr/xpg6/bin(符合SUSv3)?/usr/xpg7/bin(对于SUSv4)?
斯特凡Chazelas

@StéphaneChazelas如果我们要判断意图,我想我可以轻松地挖掘出Linus Torvalds或Theo de Raadt的报价,以表示他们对POSIX不太在意;-)而且大多数嵌入式系统都基于linux + busybox,它几乎不比典型的Unix-like系统更符合POSIX。并且a)android并不是真正的“嵌入式”系统,b)可以使android系统成为POSIX兼容无需使用shell /bin/sh
mosvy

1
@mosvy,您所说的基本上是正确的。但是Torvalds只能代表Linux内核。Chet Ramey关心ash的POSIX合规性,RedHat关心POSIX的合规性(他们赞助Austin组并参加他们的组会议,他们维护许多GNU软件)。这个想法是POSIX是大多数类Unix系统关注的唯一标准。用户关心与POSIX兼容,因为它使将软件移植到不同的系统变得更加容易。这并不理想,但总比没有好。Android有自己的编程API,POSIX并不是真正的问题。
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.