【问题标题】:Is chaining interpreters via shebang lines portable?通过 shebang 线路链接口译员是否可移植?
【发布时间】:2017-06-18 23:26:10
【问题描述】:

通过所谓的 shebang 线将脚本绑定到特定的解释器是 POSIX 操作系统上众所周知的做法。例如,如果执行以下脚本(给定足够的文件系统权限),操作系统将启动 /bin/sh 解释器,并将脚本的文件名作为其第一个参数。随后,shell 将执行脚本中的命令,跳过将被视为注释的 shebang 行。

#! /bin/sh

date -R
echo hello world

可能的输出:

Sat, 01 Apr 2017 12:34:56 +0100
hello world

我曾经相信解释器(在本例中为/bin/sh)必须是本机可执行文件,并且不能是脚本本身,而这反过来又需要又一个口译员即将推出。

不过,我还是继续尝试了以下实验。

使用下面保存为/tmp/interpreter.py的哑shell,...

#! /usr/bin/python3

import sys
import subprocess

for script in sys.argv[1:]:
    with open(script) as istr:
        status = any(
            map(
                subprocess.call,
                map(
                    str.split,
                    filter(
                        lambda s : s and not s.startswith('#'),
                        map(str.strip, istr)
                    )
                )
            )
        )
        if status:
            sys.exit(status)

…并将以下脚本另存为/tmp/script.xyz,

#! /tmp/interpreter.py

date -R
echo hello world

...我能够(在使两个文件都可执行后)执行script.xyz。

5gon12eder:/tmp> ls -l 共 8 个 -rwxr-x--- 1 5gon12eder 5gon12eder 493 Jun 19 01:01 interpreter.py -rwxr-x--- 1 5gon12eder 5gon12eder 70 Jun 19 01:02 script.xyz 5gon12eder:/tmp> ./script.xyz 2017 年 6 月 19 日星期一 01:07:19 +0200 你好世界

这让我很惊讶。我什至可以通过另一个脚本启动scrip.xyz。

所以,我要问的是:

  • 我的实验观察到的行为是否可移植?
  • 实验是否正确进行,或者是否存在这种情况不起作用?不同的(类 Unix)操作系统怎么样?
  • 如果这个 应该工作,那么就调用而言,本机可执行文件和解释脚本之间确实没有明显的区别吗?

【问题讨论】:

    标签: linux shell unix posix executable


    【解决方案1】:

    类 Unix 操作系统中的新可执行文件由系统调用 execve(2) 启动。 execve 的手册页包括:

    Interpreter scripts
        An interpreter script is  a  text  file  that  has  execute
        permission enabled and whose first line is of the form:
    
           #! interpreter [optional-arg]
    
        The interpreter must be a valid pathname for an executable which
        is not itself a script.  If the filename argument  of  execve()
        specifies  an interpreter script, then interpreter will be invoked
        with the following arguments:
    
           interpreter [optional-arg] filename arg...
    
       where arg...  is the series of words pointed to by the argv
       argument of execve().
    
       For portable use, optional-arg should either be absent, or be
       specified as a single word (i.e., it should not contain white
       space);  see  NOTES below.
    

    所以在这些限制(类 Unix,可选参数最多一个词)内,是的,shebang 脚本是可移植的。阅读手册页了解更多详细信息,包括二进制可执行文件和脚本之间调用的其他差异。

    【讨论】:

    • +1。您也可以在 linux 内核源代码中轻松找到它。我记得曾经在这里回答过一些希望 init 成为 python 脚本之类的问题,这应该可以通过相同的逻辑实现。有人敢指定一个文件作为自己的解释器吗?
    • 感谢您的快速回答,但这实际上让我更加困惑:“解释器必须是有效的路径名对于本身不是脚本的可执行文件。 ” 这不是说我应该不指定一个脚本作为解释器吗?
    • @mpez0, Re "The 手册页":由于文档在类 Unix 操作系统之间有所不同,请指定 操作系统 i> 运行 man execv 的版本。
    • @5gon12eder - 不能保证将解释器指定为脚本(“未定义行为”) - 但通常 execve 将继续链接,直到找到可执行的二进制文件。
    • @agc:Linux mint,但请您指定最近(过去 20 年)的 Unix 类操作系统,其中 execve(2) 没有这种行为?
    【解决方案2】:
    1. 请参阅下面的黑体字:

      这种机制允许脚本几乎可以在任何情况下使用 正常编译的程序可以是,包括完整的系统程序, 甚至作为其他脚本的解释器。不过,作为警告,有些 早期版本的内核支持限制了解释器的长度 指令大约 32 个字符(第一个字符只有 16 个) 实现),将无法将解释器名称从任何 指令中的参数,或有其他怪癖。此外,一些 现代系统允许整个机制受到约束或 出于安全目的而禁用(例如,set-user-id 支持已 许多系统上的脚本已被禁用)。 -- WP

    2. 这个输出来自 COLUMNS=75 man execve | grep -nA 23 " Interpreter scripts" | head -39 在 Ubuntu 17.04 盒子上, 特别是 #186-#189 行,它告诉我们在 Linux 上什么是有效的(即脚本可以是解释器,最高可达四级):

    166:解释器脚本 167-解释器脚本是具有执行权限的文本文件 168-启用,其第一行的形式为: 169- 170-#!解释器 [可选参数] 171- 172-解释器必须是可执行文件的有效路径名。 173- 如果 execve() 的文件名参数指定了一个解释器 174- 脚本,然后将使用以下参数调用解释器- 175 次: 176- 177-解释器[可选参数]文件名参数... 178- 179- 其中 arg... 是 argv 参数指向的一系列单词- execve() 的 180 次,从 argv[1] 开始。 181- 182-对于便携式使用,可选参数应该不存在,或者 183- 指定为单个单词(即,它不应包含白色 184-空格);请参阅下面的注释。 185- 186- 从 Linux 2.6.28 开始,内核允许脚本的解释器 187-本身就是一个脚本。此权限是递归的,最多为 188- 限制四次递归,这样解释器可能是一个脚本 189- 由脚本解释,依此类推。 -- 343:解释器脚本 344 - 第一个允许的最大行长度为 127 个字符 解释器脚本中的 345 行。 346- 347-解释器的可选参数的语义 348-脚本因实现而异。在 Linux 上,整个字符串 349- 在解释器名称之后作为单个参数传递给 350-解释器,这个字符串可以包含空格。如何- 351- 永远,行为在其他一些系统上有所不同。一些系统使用 352- 终止可选参数的第一个空格。在某些系统上, 353- 解释器脚本可以有多个参数,并且白色 spa- optional-arg 中的 354-ces 用于分隔参数。 355- 356- Linux 忽略脚本上的 set-user-ID 和 set-group-ID 位。

    【讨论】:

    • 谢谢,这篇文章实际上是我实验的灵感。但由于它似乎与 mpez0 引用的手册页相矛盾,我想知道这种说法是否可移植,甚至是真的。
    • 感谢您的更新。这正是我寻求的信息。我的系统(Parabola GNU/Linux)上的手册页没有说明这一点。我冒昧地更改了您答案的格式以使其更具可读性。
    • @5gon12eder,重新格式化的降价 好多了。
    【解决方案3】:

    来自 Solaris 11 exec(2) 手册页:

     An interpreter file begins with a line of the form
    
       #! pathname [arg]
    
     where pathname is the path of the interpreter, and arg is an
     optional argument. When an interpreter file is executed, the
     system  invokes  the  specified  interpreter.  The  pathname
     specified  in  the interpreter file is passed as arg0 to the
     interpreter. If arg was specified in the  interpreter  file,
     it  is  passed  as  arg1  to  the interpreter. The remaining
     arguments to the interpreter are arg0 through  argn  of  the
     originally  exec'd  file.  The interpreter named by pathname
     must not be an interpreter file.
    

    如最后一条语句所述,Solaris 根本不支持链接解释器,尝试这样做将导致最后一个非解释器(例如 /usr/bin/python3)解释第一个脚本(例如 @987654323 @,最终的命令行将变为/usr/bin/python3 /tmp/script.xyz),没有链接。

    所以脚本解释器链接根本不可移植。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多