一种调用 perl 的便携方式

一种调用 perl 的便携方式

这是来自Perl编程,第四版。这是关于执行 Perl 脚本。

最后,如果您不幸使用的是不支持魔法 #! 的古老 Unix 系统。行,或者如果解释器的路径超过 32 个字符(许多系统的内置限制),您可以像这样解决它:

#!/bin/sh -- # perl, to stop looping
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
if 0;

你能一步步解释一下这是怎么回事吗?我试图让它发挥作用或包含在内,但无济于事。

在执行上述操作时,我得到以下结果:

/bin/sh: 0: Illegal option --

答案1

这个想法是,该eval命令在 shell 和 Perl 中都有效,区别在于换行符在 shell 中终止命令,但在 Perl 中则不然。相反,Perl 读取以下行,该行添加了条件if 0,有效地否定了整个命令。

(Perl 支持一些这样的“向后”结构的速记,例如,您可以写next if $_ == 0代替if ($_ == 0) { next }, 或print for @a代替for (@a) { print }。)

如果脚本由 shell 启动,则 shell 会处理eval,并将其替换为 Perl 解释器,并为其提供脚本名称 ( $0) 及其参数 ( $@) 作为参数。

然后 Perl 运行,读取eval,跳过它(因为if 0),然后继续执行脚本的其余部分。


这就是它应该如何运作的。实际上,您会因为两件事而得到错误:1) Perl 读取 hashbang 行本身,以及 2) Linux 处理 hashbang 行的方式。

当 Perl 运行带有 hashbang 的脚本时,它并不真正将其视为注释。相反,它会解释 hashbang 中给定的 Perl 任何选项(您可以有#!/usr/bin/perl -Wln等),但它还会检查解释器并在脚本不应该由 Perl 运行时执行它!

试试这个:

$ cat > hello.sh
#!/bin/bash
echo $BASH_VERSION 
$ perl hello.sh
4.4.12(1)-release

它实际上运行着 Bash。

所以,注释#perl是告诉 Perl 是的,这实际上应该由 Perl 运行,这样它就不会启动 shell再次

但是,Linux 将解释器名称后面的所有内容都作为单个参数提供,因此当您运行脚本时,它会运行/bin/sh,并带有两个参数-- # perl, to stop looping、 和scriptname.pl。第一个以破折号开头,但不完全是--,因此 Dash 和 Bash 都尝试将其解释为选项。它不是有效的,因此您会收到错误,就像您尝试运行bash ---、 或一样bash "-- #perl"

在其他系统上,将 hashbang 行中的参数分开,-- #perl会给 shell --, #perl,并且它会尝试查找名为#perl.这又不行了。但显然有/已经有一些系统将#符号作为行中的注释标记#!,和/或仅传递第一个参数。 (看Sven Mascheck 关于此事的页面.)在这些系统上,它可能有效。


一个稍微好一些的工作在perlrun(改编):

#!/usr/bin/perl
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
    if $running_under_some_shell;
print("Perl $^V\n");

运行它bash ./script.pl实际上运行 Perl 并打印(例如)Perl v5.24.1。 ($running_under_some_shell只是一个未定义的变量,默认为 falsy。if 0会更清晰,但不那么具有描述性。)


就像您的引文中所说的那样,所有这些仅在#!无法正常工作的古代系统上才需要。在某些情况下,它根本不受支持,并且要求 shell 运行非二进制文件总是在 shell 中运行它。在其他情况下,hashbang 行中的路径长度有限制,因此#!/really/veeeery/long/path/to/perl不起作用。

只需将#!/usr/bin/perlhashbang 放在任何现代系统上即可。

答案2

解决 /bin/sh: 0: 非法选项 --

避免错误的最简单解决方案是仅调用的参数sh(在某些系统上),似乎也是将选项添加-x到 perl 调用[d]。我说似乎是因为它取决于系统、内核、shell 和用于调用脚本的 perl。

所以,最好使用类似的东西:

#!/bin/sh --
eval 'exec perl -x -S $0 ${1+"$@"}'

#!/usr/bin/perl
print("Perl $^V\n");

在上面的脚本中,perl 执行的唯一命令是打印版本。

简短的介绍

书中解释了使用此类内容的唯一原因(第 21 页):

最后,如果您不幸使用的是不支持魔法 #! 的古老 Unix 系统。行,或者如果你的解释器的路径超过 32 个字符(许多系统的内置限制),你也许可以解决它......

这是一种丑陋且复杂的替代调用的方式#!/usr/bin/perl

在同一页 21 中是您发布的脚本。关于此问题的下一个脚本位于第 577 页(继续阅读)

perl注意:仅当脚本随时被称为 时,才需要在第一行包含名称(单词) perl ./script。在这种情况下,该单词将阻止 perl 循环。事实上,第二书中的例子(第577页)是:

#!/bin/sh -- # -*- perl -*- -p
eval 'exec perl -S $0 ${1+"$@"}'
if 0;

其中(# -- 珀尔 -- -p) 在书中被解释为由 perl 解释(并且大多数被忽略)。

然而,这是假设 shell 调用#!/bin/sh将允许多个 ( --) 参数。在某些系统中情况并非如此。最好将其移至下一行(如果 emacs 需要)和/或删除:

#!/bin/sh -- # 
# -*- perl -*- -p
eval 'exec perl -S $0 ${1+"$@"}'
if 0;

一种调用 perl 的便携方式

要了解其基础知识,首先要了解的概念是脚本 *可能** 被处理两次,一次由 shell 处理,然后由 shell 调用的解释器(在本例中为 perl)处理。

了解该脚本可能被称为 as sh scriptbash ./script、简称为./script、 或者,如果路径包含当前工作目录,则称为 as ,script甚至称为 as/usr/bin/perl script或 as perl script。在 DOS、Unix、Windows、Linux 等中

如何调用适用于所有系统的perl?

壳程

此代码(cat ./script):

#!/bin/sh -- 
eval 'exec perl -S $0 ${1+"$@"}'
if 0;

几乎可以被任何 shell 解释(称为./scriptshell ./script如下:

  1. 内核将读取第一行,将其识别为 shebang,并启动放置在/bin/sh文件(或链接)中的 shell 可执行文件。此外,内核将接受并处理--作为表示“选项结束”的参数[a] [b]。那就是:

    #!/bin/sh --
    
  2. shell(假设 中有一个 shell /bin/sh)将读取并执行第一行(未注释):

    eval 'exec perl -S $0 ${1+"$@"}'
    

    第一个单词(eval)会将参数列表转换为命令行以执行删除引用层。也就是说,它将被转换为这样的命令行:

    exec perl -S $0 ${1+"$@"}
    

    这个(新)命令行的第一个单词(即:)exec将告诉内核用可执行文件perl(在 PATH 中搜索)和扩展命令行其余部分所产生的参数替换正在运行的进程。

第一个参数 ( -S) 将告诉 perl 也搜索下一个参数 ( $0) 中指定的可执行脚本。参数$0(通常)是先前加载的 shell 脚本的名称(包含您发布的代码,又称为./script)。

下一个参数:${1+"$@"}意思是:如果 arg$1存在且不为 null ( ${1+...}) 将整个参数替换为 的扩展结果"$@"。的展开"$@"是列表全部加载脚本的参数。

您可以通过测试查看此命令行中给出的参数:

    #!/bin/sh -
    eval 'exec /usr/bin/printf "<%s> " -S $0 ${1+"$@"}'

调用这个脚本:

    $  ./script one two t33 f44 "f55 s66"
    <-S> <./script> <one> <two> <t33> <f44> <f55 s66>

因此,原始脚本中的该行将调用 perl (等效)为:

    $ perl -S ./script one two t33 f44 "f55 s66"

对 perl 的调用替换了正在执行的 shell 脚本 (exec)。

Perl 端

然后 Perl 加载脚本并重新解释其内容。
Perl 中的注释也以#
因此,第一行 (shebang) 是对 Perl 的注释。

第二行:如果下一行是,eval 'exec perl -S $0 ${1+"$@"}'则将由 perl 解释(这eval是一个有效的 Perl 命令)if 0;不是那里。这第三行使 Perl不是执行第二行。写一个很长的方法nop

然后脚本的其余部分由 Perl 解释并执行。

请注意,所有这些构造都是一种非常复杂的编写方式:

#!/usr/bin/perl

为什么真的需要它?

设置路径

一个“你好世界”的例子如同并使用-xperl 选项作为通常在 perldoc 网站上推荐

将 -x 调用添加到 perl 将确保 perl 本身会搜索#!/usr/bin/perlshebang(可能是第一行下方的许多行)以开始执行脚本。这-x也会显得if 0多余。

#!/bin/sh -
eval 'PATH="/usr/bin:/bin:/usr/local/bin:$PATH";: \
;exec perl -x -S -- "$0" ${1+"$@"};#'if 0;

#!/bin/perl -w
# Above is magic header ... real Perl code begins here
use strict;
use warnings;
print "hello world!\n";

这将设置一个更加可移植的路径来搜索 perl 可执行文件。可以设置更多变量以使对 perl 的调用更加可靠(如果需要)。

[d] perl选项-x将搜索具有 perl 名称的 shebang 行:#!/bin/perl -w以开始从 perl 解释脚本。这使得 shell 和 perl 之间的区别更加清晰。

如果该脚本被调用为:

$ ./script
hello world!

$ sh ./script
hello world!

$ perl ./script
hello world!

但如果从cshshell 调用将会失败。通过添加eval '(exit $?0)'测试和新的执行程序可以解决这个问题:

#!/bin/sh --
eval '(exit $?0)' && eval 'PATH="/usr/bin:/bin:/usr/local/bin:$PATH";: \
     ;exec perl -x -S -- "$0" ${1+"$@"};#'if 0;
exec 'exec perl -x -S -- "$0" $argv:q;#'.q

用于启动 Perl 脚本的神奇标头
相关的长描述。

[a]后面的整个字符串#!/bin/sh可能被解释为只有一个参数。在这种情况下,整个字符串-- # perl, to stop looping可能会被报告为错误。就像 bash 在执行时直接加载脚本一样./script(而运行的 shell 是 bash)。阅读更多详细信息搜索“分割参数”

[b]在极少数情况下,使用 a 很有用。--使用 a-代替 as更便携在这个问题中解释了

相关内容