Bang Bang
解释器读取并执行脚本(而外壳更像是一个厨房通道,可以执行或传递给另一个解释器)。当我们指定命令行上的解释器时,它就是将被使用的那个。例如,Rscript script.R
将使用 Rscript
解释器执行 script.R。
当我们执行一个文件而没有明确指定解释器(例如,像 ./myscript.pl
),那么“shebang”的任务就是告诉外壳/操作系统使用哪个解释器。shebang 是文本文件的第一行,以 #!
开头,后跟解释器路径
#!/usr/bin/perl
有时我们会看到 env
程序,它在我们路径中找到第一个 perl
#!/usr/bin/env perl
env
不分割参数,因此我们无法添加选项
#!/usr/bin/env perl -w
并且,env
并不一定位于 /usr/bin/env
,因此它可以在机器/分发级别保证一些可移植性,但并不总是在分发之间。
Perl 很好
perl
与其他解释器不同——它很好,即使有挑战。 perl
检查 shebang 以确定它是否真的适用于它(如果不是,它将我们的程序传递给另一个解释器)。
例如,文件 i-am-python.pl 包含一个 Python 程序,这绝对不是 Perl
#!/usr/bin/python
import os
import time
print("I'm a snake : " + os.environ["SHELL"] + " " + os.environ["_"])
# Keep it alive to have time to inspect with ps
while True:
time.sleep(5)
显然,我们不关心扩展名,因为它并不意味着任何类型的文件关联(尽管一些系统允许你关联它)。所以我们有一个 .pl 文件,我们用 perl
来执行它,但里面有一个 python
shebang 和一些 Python 代码。很明显,这不是一个有效的 Perl 文件。
如果你不相信我,可以用一个快速的语法检查来验证 perl -c i-am-python.pl
,它告诉我们它不是一个有效的 Perl
$ perl -c i-am-python.pl
syntax error at i-am-python.pl line 3, near "import time"
i-am-python.pl had compilation errors.
当我们用 perl
执行这个文件时,令人惊讶的是,一切都很顺利。这是怎么回事?perl
足够聪明,可以将脚本传递给 python
!
$ perl i-am-python.pl
I'm a snake : /bin/bash /usr/bin/perl
如果我们想检查哪个解释器真正运行了这个脚本,我们可以查看进程表
$ ps aux | grep "i-am-pytho[n].pl"
tduponc+ 5647 0.0 0.0 33208 7024 pts/0 S 13:04 0:00 /usr/bin/python i-am-python.pl
注意 i-am-pytho[n].pl
,其中方括号将 n
放在字符类中。这是一个巧妙的技巧,以便 grep
能够找到包含 python
的行,但不是 grep
进程本身,因为该模式不会与字面量 [
匹配。
别忘了杀死程序,因为它会永远休眠!
现在,如果我们想测试相反的情况,用 python
解释器运行 Perl 代码怎么办?
#!/usr/bin/perl
my $str = "I'm a jewel";
print "$str : $ENV{SHELL} $ENV{_}\n";
while (1) { sleep 5; }
这是一个有效的 Perl 文件,但 python
解释器不会传递给 perl
,而是返回一个 Python 错误
$ python i-am-perl.py
File "iamperl.py", line 3
my $str = "I'm a jewel";
^
SyntaxError: invalid syntax
这特别适用于 Python。你自己用 bash、Ruby 或其他东西试试。
我有一件事要告诉你
命令行上有正确的解释器并不意味着 shebang 完全被忽略。 perl
再次非常聪明,表现得就像我们想象的那样(DWIM)。例如,如果我们把警告开关(-w
)放在 shebang 中,就像这个文件 override-bang.pl 一样
#!/usr/bin/perl -w
$str = "will produce a warning";
即使我们没有在命令行上添加 -w
,我们仍然会收到警告
$ perl override-bang.pl
Name "main::str" used only once: possible typo at override-bang.pl line 3.
足够多不是瘟疫
现在,如果我们指定了一些命令行开关和一些 shebang 中的开关,会发生什么?剧透:它们只是简单地合并在一起。
当我们运行perl -c overridebang.pl
来检查语法正确的文件时,我们会从命令行和shebang行获取开关。我们得到一个perl -cw
执行
Name "main::str" used only once: possible typo at override-bang.pl line 5.
override-bang.pl syntax OK
如果我们有像-w
启用警告和-X
禁用警告这样的冲突选项怎么办?这里有一个enable-warnings.pl
#!/usr/bin/perl -w
$str = "will produce a warning";
当我们单独运行这个脚本时,我们会得到预期的警告
$ perl enable-warnings.pl
Name "main::str" used only once: possible typo at warnings.pl line 3.
当我们命令行中添加-X
时,没有输出
$ perl -X enable-warnings.pl
那么将-X
放在shebang行呢?这里是一个disable-warnings.pl
#!/usr/bin/perl -X
$str = "will produce a warning";
当我们用-w
运行这个脚本时,我们仍然没有输出
$ perl -X enable-warnings.pl
-X
总是关闭警告。
shebang(-X
)的优先级高于命令行,没有报告警告。如果我们用perl -W disable-warnings.pl
执行文件也是一样。
我们可以想象这是一个解决“最后看到的”参数冲突的规则,但是等等,事情并不那么简单。
那么-X
和启用所有警告的-W
相比,谁会赢呢?结果是定义在最后的参数会赢。我们可以在命令行上看到这一点
$ perl -W -X -e '$str = "will produce a warning"'
$ perl -X -W -e '$str = "will produce a warning"'
Name "main::str" used only once: possible typo at -e line 1.
作为读者的练习,尝试不同的taint检查选项组合:-T
和-U
。
一个魔法咒语
有时我们在Perl程序的开头看到一些奇怪的行。这是什么黑魔法?这实际上是一个非常聪明的“多语言”打开方式,对shell(有或没有shebang支持)和perl
都是正确的
#!/usr/bin/perl
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
if $running_under_some_shell;
如果我们用perl
开始脚本,任务就完成了,perl
会执行
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
if $running_under_some_shell;
$running_under_some_shell
没有值,所以代码翻译成false条件。这一行被忽略,文件的其他部分正常解释。
eval 'exec /usr/bin/perl -S $0 ${1+"$@"} if 0;'
如果我们用识别shebang的shell开始脚本呢?shell将任务传递给perl
,然后读取第一行(shebang然后eval ...
)。然后执行流程与上面相同(魔法咒语不起作用,文件被解释)。这并不令人惊讶。
但是,如果我们用一个不识别shebang的shell开始脚本呢?没有立即发生传递。这正是这个魔法有用的地方。shell会忽略第一行,永远不会到达第三行。为什么永远不会到达第三行呢?换行符终止shell命令,exec
将替换当前的执行为perl
。从那个exec
之后,脚本的其他部分不再重要。我们的代码从这样
#!/usr/bin/perl
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
if $running_under_some_shell;
有效变为这样
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
那些$0
和$@
是shell脚本名和参数的词,而-S
告诉perl
使用PATH环境变量在$0
中查找值。(perldoc)
-x很有趣
我们已经和perl
解释器和shebang玩得很开心了,但是perl
有一个-x
选项,它本身就很有趣。这个选项告诉Perl要执行的程序实际上嵌入在一个更大的无关文本块中,忽略它。也许Perl程序在电子邮件消息的中间
"I do not know if it is what you want, but it is what you get.
-- Larry Wall"
#!/usr/bin/env perl
print "perl -x ignores everything before shebang\n";
print <DATA>;
__END__
"Fortunately, it is easier to keep an old interpreter around than an
old computer.
-- Larry Wall"
将这个作为程序执行是语法错误,因为shebang之前的Larry Wall引言不是有效的Perl。当我们用perl -x
执行这段代码时,shebang之前的所有内容都被忽略,它正常工作
$ perl -x email.txt
perl -x ignores everything before shebang
"Fortunately, it is easier to keep an old interpreter around than an
old computer.
-- Larry Wall"
出于好奇,如果我们再进一步尝试会怎样?文件中有没有多个shebang,其中一个有-x
?
#!/usr/bin/perl -x
#!/usr/bin/perl
但是它只产生了一个错误
Can't emulate -x on #! line.
但是有一个技巧可以实现这一点,通过使用shell的eval
。现在那个perl -x
是在shell进程中执行的,而不是像以前那样由perl二进制文件解释。
#!/bin/sh
eval 'exec perl -x $0 ${1+"$@"}'
die "another day"; exit 1
#!perl
print "$]\n";
startperl
这篇文章如果不讨论一下配置变量$Config{startperl}
就不会完整。这个变量来自Config.pm,它提供有关配置环境的信息(你还可以用perl -V
看到它)
$ perl -e 'use Config; print $Config{startperl}'
#!/usr/bin/perl
这实际上是在编译过程中从默认值或用户/供应商提供的配置中构建的。如果我们想使用不同的值呢?只需在./Configure
步骤中指定此值,配置选项为-Dstartperl='...'
。然后我们需要重新构建perl
$ ./Configure -des -Dstartperl='#!/my/shebang'
$ make test install
现在我们的自定义值是默认值
$ perl -e 'use Config; print $Config{startperl}'
#!/my/shebang
ExtUtils::MakeMaker和Module::Build似乎也使用startperl
等其它方法来修复模块的shebang。
请注意使用一个表现得像perl
解释器的解释器或程序!一些CPAN模块使用startperl
来写入生成的perl测试的第一行。这里仍然适用/usr/bin/env
的限制。
资源
标签
反馈
这篇文章有什么问题吗?请在GitHub上通过GitHub提交一个issue或pull request来帮助我们。