Shell脚本头部 (#!/bin/sh vs #!/bin/csh)

143

为什么所有的脚本文件都以 </ 开头?

#!/bin/sh
或者使用。
#!/bin/csh

那是必需的吗?这是什么目的?两者之间有什么区别?


1
对于csh脚本,您应该使用#!/bin/csh -f; -f告诉shell不要源用户的.login.cshrc,这使得脚本运行更快,并避免了对用户设置的依赖。(或者最好不要编写csh脚本。)不要为sh或bash脚本使用-f;它没有相同的含义。 - Keith Thompson
这是一个公正的问题,也是一个经典问题,但“是否需要”也可以通过实证来回答。大多数情况下...不需要。 - Dan Rosenstark
3个回答

137

这被称为Shebang:

http://en.wikipedia.org/wiki/Shebang_(Unix)

#!解释器 [可选参数]

只有在脚本具有执行权限时(例如chmod u+x script.sh),才与 Shebang 相关。

当 shell 执行脚本时,它将使用指定的解释器。

例如:

#!/bin/bash
# file: foo.sh
echo 1

$ chmod u+x foo.sh
$ ./foo.sh
  1

@Kolob Canyon,你不需要这样做,但它可以帮助一些编辑器进行语法高亮(尽管通常有其他方法来实现相同的效果):https://unix.stackexchange.com/a/88730/193985 - Braham Snyder

60
#!这一行告诉内核(具体来说,是execve系统调用的实现),这个程序是用解释型语言编写的;随后的绝对路径标识了解释器。编译成机器代码的程序以不同的字节序列开头--在大多数现代Unix中,是7f 45 4c 46 (^?ELF),以此来辨别它们。
只要该程序本身不是一个#!脚本,你可以在#!后面放置任何程序的绝对路径。内核会重新编写调用。
./script arg1 arg2 arg3 ...

当以#! /usr/bin/perl开头的时候,./script就会开始运行,就好像命令行实际上是这样的

/usr/bin/perl ./script arg1 arg2 arg3

或者,正如您所见,您可以使用#! /bin/sh编写一个脚本,该脚本旨在由sh解释。 #!行仅在您直接调用脚本(在命令行上输入./script)时才会被处理;文件还必须是可执行的(chmod +x script)。如果您执行sh ./script,则不需要#!行(如果存在,则将被忽略),并且文件不必是可执行的。此功能的目的是允许您直接调用解释型语言程序,而无需知道它们是用哪种语言编写的。(执行grep '^#!' /usr/bin/*——您将发现许多标准程序实际上都在使用此功能。)
以下是使用此功能的一些规则:
  • #!必须是文件中最前面的两个字节。特别地,文件必须采用兼容ASCII的编码(例如UTF-8可以使用,但UTF-16不行),并且不能以“字节顺序标记”开头,否则内核将无法识别它为#!脚本。
  • #!后面的路径必须是绝对路径(以/开头)。它不能包含空格、制表符或换行符。
  • #!/之间加上一个空格是好的风格,但不是必需的。请不要在这里放置多个空格。
  • 您不能在#!行上放置shell变量,因为它们不会被扩展。
  • 您可以在绝对路径后面放置一个命令行参数,通过单个空格与绝对路径分隔开来。与绝对路径一样,此参数不能包含空格、制表符或换行符。有时候这是必要的,以使事情正常工作(#! /usr/bin/awk -f),有时候这只是有用的(#! /usr/bin/perl -Tw)。不幸的是,您不能在绝对路径之后放置两个或多个参数。
  • 有些人会告诉你使用#! /usr/bin/env interpreter而不是#! /absolute/path/to/interpreter。这几乎总是一个错误。它使您的程序的行为取决于调用脚本的用户的$PATH变量。并且并非所有系统都有env
  • 需要setuidsetgid权限的程序无法使用#!;它们必须被编译为机器代码。(如果您不知道setuid是什么,请不要担心这个)。

关于csh,它与sh的关系大致如同Nutrimat Advanced Tea Substitute和茶的关系。对于交互使用,它比sh有一些优势(或者说曾经有过,现代实现的sh已经追赶上来了),但是将其(或其后代tcsh)用于脚本编写几乎总是错误的。如果你是初学者,我强烈建议你忽略它,专注于sh。如果你正在使用csh相对作为登录shell,请切换到bashzsh,这样交互式命令语言就与你正在学习的脚本语言相同。


Linux的最新版本允许指定解释器为脚本。通常省略#!后面的空格,对此是否是良好的风格不做评论。请参阅此问题我的答案,以了解#!/usr/bin/env hack的利弊讨论。 - Keith Thompson
@KeithThompson 我的印象是Linux是唯一允许解释器为脚本的常见Unix变体,因此仍然不是可靠的东西。自从我写了这篇文章以来,我自己遇到了一种情况,其中#!/usr/bin/env是正确的选择,但我仍然认为这几乎总是一个坏主意。 - zwol

9
这段话介绍了用于解释/运行脚本的shell(命令解释器)是什么,每个shell在与用户交互和执行脚本(程序)方面都略有不同。当你在Unix提示符下输入命令时,你正在与shell交互。例如,#!/bin/csh表示C shell,/bin/tcsh表示t shell,/bin/bash表示bash shell等。你可以通过命令找出你正在使用的交互式shell。
 echo $SHELL

命令,或者另一种选择是:
 env | grep -i shell

您可以使用chsh命令更改您的命令shell。
每个shell都有略微不同的命令集和分配变量的方法,以及自己的编程结构。例如,在bash中,if-else语句与C-shell中的if-else语句看起来不同。 这个页面可能会引起您的兴趣,因为它“翻译”了bash和tcsh命令/语法之间的差异。
在shell脚本中使用指令可以让您使用不同的shell运行程序。例如,我在交互式环境中使用tcsh shell,但经常使用脚本文件中的/bin/bash运行bash脚本。
此概念也适用于其他脚本。例如,如果您使用Python进行编程,将在脚本文件中放置:
 #!/usr/bin/python

在你的Python程序顶部


那么这是必需的吗?我怎么知道我真正使用的是哪个 shell? - One Two Three
如果我正在为某人编写脚本,但不知道他们正在使用哪个shell。(很不幸,这个人对这些东西一无所知,因此他只能运行脚本而不改变任何东西)。我能做类似于#! $SHELL的事情吗?这样会将正确的shell放入Shebang中吗? - One Two Three
1
@OneTwoThree 大多数系统都有标准的 shell,如果你编写一个 bash 或 csh 脚本,那么就没问题了。无论他们交互式地使用什么 shell 都不重要,这就是例如 !#/bin/bash 指令的美妙之处。它告诉系统使用哪个 shell 来执行你的 shell 脚本。 - Levon
$SHELL 的值并不能确定你当前正在使用哪个 shell;它通常只是告诉你你的默认 shell。tcsh 设置 $version$tcsh;bash 设置 $BASH_VERSION。并非所有 shell 都必须有类似的机制。 - Keith Thompson
1
@OneTwoThree:#! 行必须与脚本的语法匹配,而不是运行脚本的交互式 shell 的语法。 - Keith Thompson

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