编写可在任何 shell 上运行的 shell 脚本(使用多个 shebang 行?)

swr*_*bel 21 bash zsh shell shell-script bash-scripting

我刚刚开始更深入地研究 shell 脚本,我总是把我的脚本放在一个文件中,标记它chmod +x然后完成/path/to/script.sh并让任何默认的解释器使用它,我认为它是 zsh,因为这就是我用于我的外壳。显然,它似乎只是/bin/sh默认情况下,即使我从 zsh 提示符执行脚本,因为我开始将特定于 zsh 的东西放入我的脚本中,除非我运行zsh /path/to/script.sh.

为了切入正题,以下是我的问题:

  1. #!/path/to/shell开头没有 shebang 行 ( )时,哪个 shell 执行脚本?我假设/bin/sh但我无法确认。
  2. 在编写可在任何平台上运行的 shell 脚本方面,什么被认为是“最佳实践”?(好吧,这是一种开放式)
  3. 如果 zsh 不可用,是否可以编写一个尝试使用 zsh 并回退到 bash 的脚本?我试过放置两条shebang行,如下所示,但是bad interpreter: /bin/zsh: no such file or directory如果我在没有zsh的机器上尝试它只会出错。

    #!/bin/zsh

    #!/bin/bash

use*_*686 28

当开头没有 shebang 行 (#!/path/to/shell) 时,哪个 shell 执行脚本?我假设 /bin/sh 但我无法确认。

内核拒绝执行此类脚本并返回 ENOEXEC,因此确切的行为取决于您.

  • bash 4.2.39 – 使用自身
  • busybox-ash 1.20.2 – 使用自己
  • 破折号 0.5.7 - 运行 /bin/sh
  • fish 1.23.1 – 抱怨 ENOEXEC,然后指责错误的文件
  • AT&T ksh 93u+2012.08.01 – 使用自身
  • mksh R40f – 运行 /bin/sh
  • pdksh 5.2.14 - 运行 /bin/sh
  • sh-heirloom 050706 – 自用
  • tcsh 6.18.01 – 运行 /bin/sh
  • zsh 5.0.0 – 运行 /bin/sh
  • cmd.exe 5.1.2600 – 看着你很有趣。

glibc 中,函数execv()execve()只返回 ENOEXEC。但execvp()隐藏此错误代码并自动调用 /bin/sh。(这记录在exec(3p) 中。)

在编写可在任何平台上运行的 shell 脚本方面,什么被认为是“最佳实践”?(好吧,这是一种开放式)

要么坚持sh并且只使用 POSIX 定义的功能,要么只是使用完整的bash(广泛可用)并在分发它时在您的要求中提及它。

(现在我想起来了,Perl——或者也许是 Python——会更便携,更不用说有更好的语法了。)

始终添加shebang 行。如果使用 bash 或 zsh,请使用#!/usr/bin/env bash而不是硬编码 shell 的路径。(但是,POSIX shell 保证在/bin/sh,所以env在这种情况下跳过。)

(不幸的是,即使/bin/sh不总是相同的。GNU autoconf程序必须处理许多不同的怪癖。)

如果 zsh 不可用,是否可以编写一个尝试使用 zsh 并回退到 bash 的脚本?我试过把两行shebang行,如下所示,但它只是错误解释器的错误:/bin/zsh:如果我在没有zsh的机器上尝试没有这样的文件或目录

只能有一个shebang线;换行符之后的所有内容都不会被内核读取,而是被 shell 视为注释。

可以编写一个脚本,运行的#!/bin/sh,检查其外壳是可用的,并且运行exec zsh "$0" "$@"exec bash "$0" "$@"取决于结果。然而,bash 和 zsh 使用的语法在不同的地方是如此不同,我不建议这样做

  • @SWrobel:使用`strace -f -e fork,clone,execve`。一些shell在失败后继续执行`/bin/sh`;其他人自己解释了剧本。 (3认同)
  • 另请参阅 [Stéphane Chazelas 的回答](https://unix.stackexchange.com/q/373223/80216#373294) [Which shell interpreter running a script without shebang?](https://unix.stackexchange.com/ q/373223/80216)(子问题#1 的跨站点重复) (3认同)
  • @SWrobel:另一种方法是运行由`readlink /proc/$$/exe` 组成的脚本。 (2认同)
  • 对于 `csh` 和 `tcsh`,行为取决于脚本是否以 `#` 开头(在这种情况下,它们调用自己而不是 sh 来解释脚本)。这可以追溯到 csh 支持注释但不支持 Bourne shell 的时代,因此 `#` 暗示它是一个 csh 脚本。 (2认同)