什么是推荐的POSIX sh shebang?

19

我读过这样的文章,如果想以可移植的方式使用Bash,则应使用shebang:

#!/usr/bin/env bash

但是现在我想知道:当我想明确说明我不依赖Bash,而是编写了遵循POSIX标准的脚本时,我应该使用:

#!/bin/sh

还是在这里也最好使用#!/usr/bin/env sh


2
“#!/usr/bin/env sh” 这样的写法是不是有些得不偿失呢?如果 /usr 目录在单独的分区上,那么带有 “#!/bin/sh” 的脚本可以在 /usr 没有挂载的情况下使用,而 “#!/usr/bin/env sh” 则会失败,尽管 sh 在 /bin 中。我同意这种情况比较少见,但仍然存在。 - el.pescado - нет войне
确实,对于默认 Shell(如 sh),这可能是一个问题,但对于其他 Shell(例如 bash),似乎是必要的。参考资料 - JepZ
即使使用bash,它也扩展了可移植的定义。它只是说“使用您路径中首先命名为bash的程序”。它没有说明该版本的bash应该是什么(3.2?4.0?4.1?4.2?4.3?4.4?5.0?),甚至没有说命名为bash的程序是否真的是Bash。记录运行脚本所需的内容;让用户担心如何指定它。 - chepner
2个回答

21

正式角度

POSIX规范sh:应用程序使用信息部分指出,您不能依赖于sh可执行文件安装在/bin/sh路径下。

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

例如,要确定标准sh实用程序的位置:

command -v sh
然而,它建议在安装时修改shell脚本以使用完整的sh路径,而不是建议使用env来使用适当的PATH:
此外,在支持可执行脚本的系统上(“#!”结构),建议应用程序使用getconf的PATH确定shell路径名并在安装时相应地更新“#!”脚本(例如,使用sed)。
实践中,我主要编写POSIX shell脚本,实际上每个GNU/Linux系统(Red Hat和Debian-based)-和其他如Cygwin和OS X - 都有符合POSIX标准的sh,安装到/bin/sh或可用作该路径的软链接或硬链接。我从未需要使用env来适应不使用此路径的sh的系统。
可能存在一些Unix系统,其中没有作为/bin/sh可用的符合POSIX标准的sh。 POSIX规范建议在某些系统上安装为/usr/xpg4/bin/sh。据我所知,这对于Solaris系统是真实(过去?),其中/bin/sh是早期版本的Bourne shell,它在POSIX之前发布。在这种情况下,使用env sh不能保证有所帮助,因为它仍然可能在/usr/xpg4/bin/sh上找到POSIX shell之前在/bin/sh处找到Bourne shell。
概括起来,如果您要为通用Unix和Linux操作系统编写POSIX shell脚本,请使用#!/bin/sh作为shebang。
在极少数情况下,/bin/sh是Bourne shell而不是符合POSIX标准的shell,则必须修改shebang以使用适当的完整路径到POSIX shell。
在任何情况下,使用#!/usr/bin/env sh没有好处,与其使用#!/bin/sh相比可能更容易失败。

1
好的,所以你的建议是尽可能使用安装工具(例如configure、make),否则保持使用“#!/bin/sh”? - JepZ
1
@JepZ 我已经更新了我的答案。希望现在更清晰了。 - Anthony Geoghegan
2
那些想在Solaris上使用POSIX语义的人不是会确保在他们的PATH中/usr/xpg4/bin出现在/usr之前吗? - tripleee
@JepZ 这就是我会推荐的。对于开发者来说,shebang只是一个“建议”;而对于安装程序来说,它知道本地系统上正确解释器的位置。 - chepner
Python安装工具将替换任何包含python的shebang(惯例上为#!python作为最小示例)为正确和适当的安装程序指定路径。(这处理了“非标准”路径和Python 2和Python 3之间的差异,两者都可以本地安装。) - chepner
显示剩余2条评论

0

我会说 #!/usr/bin/env sh 更可取。

一些可移植的构造,例如循环遍历find的结果, 需要显式调用 sh。请考虑此脚本:

#!/bin/sh
# ... some commands here ...
find . -exec sh -c '
  for file do
    # ... commands processing "$file" ...
  done' find-sh {} +

开头的命令将由/bin/sh运行,而处理"$file"的命令将由PATH中第一个出现的sh运行,这可能与/bin/sh的行为不同。这是意外错误的潜在来源。 #!/usr/bin/env sh shebang解决了这个问题,因为所有命令都将由PATH中第一个sh运行。

#!/usr/bin/env sh shebang唯一的潜在缺点是在调用脚本时/usr可能没有挂载。然而,在实践中,这种情况不应经常发生。在可移植脚本中经常使用的外部程序,例如awk,也经常在/usr/bin中找到,因此很难确保脚本在未挂载/usr的情况下正确运行。

如果您真的想要可移植性并且不依赖于/usr被挂载,可以按以下方式开始脚本,以确保它始终由PATH中的sh执行:

#!/bin/sh
if test X"$SUBSHELL" != X"1"; then
  SUBSHELL=1
  export SUBSHELL
  exec sh "$0" "$@"
  exit 1
fi

# ... your actual script comes here ...

但这似乎有点过度,所以我认为#!/usr/bin/env sh shebang是一个合理的折衷方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接