在Bash脚本中,#!/bin/false的目的是什么?

6
在处理我前同事编写的bash项目时,我注意到所有的.sh文件都只包含以#!/bin/false开头的函数定义。据我所知,这是一种安全机制,可以防止仅包含引用的文件被执行。 示例: my_foo.sh
#!/bin/false
function foo(){
    echo foontastic
}

my_script.sh

#!/bin/bash

./my_foo.sh # does nothing
foo # error, no command named "foo"

. ./my_foo.sh 
foo # prints "foontastic"

然而,如果我不使用#!/bin/false,正确使用和错误使用的效果是完全相同的:

例子:

my_bar.sh

function bar(){
   echo barvelous
}

my_script.sh

 #!/bin/bash

./my_bar.sh # spawn a subshell, defines bar and exit, effectively doing nothing
bar # error, no command named "bar"

. ./my_bar.sh
bar # prints "barvelous"

既然在两种情况下都通过使用source正确地包含这些脚本可以按预期工作,并且从父shell的角度来看,在两种情况下执行它们不会产生任何效果,也不会生成关于无效使用的错误消息,那么这些脚本中的#!/bash/false究竟是什么目的呢?


2
你自己说了:使用 false shebang,脚本内容就不会被执行。这是一种安全保障,以防脚本在错误的上下文或资源繁重的情况下出错。很可能,这是你同事养成的习惯,在大多数情况下对代码没有影响,但在更复杂的情况下可能会有用。 - Aserre
值得注意的是,当您直接调用shell解释器时(如bash my_script.sh),she-bang行#!/bin/true#!/bin/false对脚本没有影响,这个语法无论如何都会使用bash运行脚本并输出foontastic - Inian
2
./my_bar.sh 不会生成子shell:它会生成一个全新的shell。这不仅仅是技术上的细节:子shell会接收到主shell的所有变量的副本(包括那些没有标记为导出的变量),而单独的shell只会接收到导出的变量。子shell可以通过其他方式启动,例如通过括号 ()、管道 |、进程/命令替换 $() <() >() 等。 - Fred
2个回答

5

一般来说,我们考虑一个名为testcode的文件,其中包含bash代码。

#!/bin/bash
if [ "$0" = "${BASH_SOURCE[0]}" ]; then
  echo "You are executing ${BASH_SOURCE[0]}"
else 
  echo "You are sourcing ${BASH_SOURCE[0]}"
fi

你可以通过它完成三件不同的事情:

$ ./testcode
You are executing ./testcode

如果 testcode 有正确的权限和正确的 shebang,那么这将起作用。使用 #!/bin/false 的 shebang,这将不输出任何内容并返回代码 1(false)。

$ bash ./testcode
You are executing ./testcode

这完全忽略了shebang(甚至可能缺失),只需要读取权限,而不需要执行权限。这是在Windows的CMD命令行中调用bash脚本的方法(如果你的PATH中有bash.exe),因为在那里shebang机制不起作用。
$ . ./testcode
You are sourcing ./testcode

这也完全忽略了上面提到的shebang,但这是一个完全不同的问题,因为“源脚本”意味着“当前shell执行它”,而“执行脚本”意味着“调用新shell来执行它”。例如,如果在已经被源的脚本中放置了一个exit命令,则会退出当前shell,这很少是您想要的。因此,通常使用源来加载函数定义或常量,有点类似于其他编程语言的import语句,各种程序员开发出不同的习惯来区分要执行的脚本和要源的“包含文件”。我通常不对前者使用任何扩展名(其他人使用.sh),但我对后者使用.shinc扩展名。您以前的同事使用了#!/bin/false的shebang,只有他们自己知道为什么喜欢这个选项,其中一个原因是您可以使用file来区分这些文件:
$ file testcode testcode2
testcode: Bourne-Again shell script, ASCII text executable
testcode2: a /bin/false script, ASCII text executable

当然,如果这些包含文件只包含函数定义,则执行它们是无害的,因此我认为您的同事并不是为了防止执行而这么做。
受Python世界的启发,我还有一个习惯,就是将一些回归测试放在我的.shinc文件末尾(至少在开发时)。
... function definitions here ...

[ "$0" != "${BASH_SOURCE[0]}" ] && return 

... regression tests here ...

由于return在执行脚本时会生成错误,但在源代码中使用却是可以的,因此获取相同结果的一种更加隐晦的方法是:

... function definitions here ...

return 2>/dev/null || :

... regression tests here ...

1
使用#!/bin/false和不使用的区别在于父shell返回值的不同。 /bin/false总是返回一个失败的返回值(在我的情况下是1,但不确定是否标准)。
尝试一下:
./my_foo.sh //does nothing
echo $? // shows "1", a.k.a failing

./my_bar.sh //does nothing
echo $? // shows "0", a.k.a. everything went right

因此,使用 #!/bin/false 不仅记录了脚本不打算执行的事实,而且在执行时还产生错误返回代码。


你的第二个例子是不正确的,如果my_bar.sh包含“exit 1”。另外,当包含这些文件而不是执行(“. ./my_foo”,“. ./my_bar”)时,返回值实际上是最后一个执行的命令的返回值,因此为了将返回值用作保障措施,我需要以“true”结束所有仅包含脚本。 - Reverent Lapwing
OP提供的脚本my_bar.sh没有包含一个exit命令。此外,返回代码只是脚本的返回代码。问题在于使用#!/bin/false将生成错误的返回代码。 - A.Perrot

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