【问题标题】:shebang: use interpreter relative to the script pathshebang:使用相对于脚本路径的解释器
【发布时间】:2013-11-20 11:58:16
【问题描述】:

我尝试构建可以随时随地使用的脚本。为此,我使用了一个定制的 python,它始终位于相对于脚本的父目录中。

这样我可以将我的包加载到 U 盘上,它可以在任何地方工作,无论 U 盘安装在哪里以及是否安装了 python。

但是,当我使用

#!../python

那么它只有在脚本从其目录中被调用时才起作用,这当然是不可接受的。

有没有办法做到这一点,或者在当前的 shebang 机制中这是不可能的?

【问题讨论】:

    标签: python shebang


    【解决方案1】:

    this page 上有一组健康的多行 shebang 脚本,适用于多种语言,例如:

    #!/bin/sh
    "exec" "`dirname $0`/python" "$0" "$@"
    print copyright
    

    如果您想要单行 shebang,this answer(和问题)会详细解释问题并建议使用 shebang 中的其他脚本的以下方法:

    使用 AWK

    #!/usr/bin/awk BEGIN{a=ARGV[1];sub(/[a-z_.]+$/,"python",a);system(a"\t"ARGV[1])}
    

    使用 Perl

    #!/usr/bin/perl -e$_=$ARGV[0];exec(s/\w+$/python/r,$_)
    

    21 年 1 月 11 日更新:

    使用更新的env 实用程序:

    $ env --version | grep env
    env (GNU coreutils) 8.30
    $ env --help
    Usage: env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]
    Set each NAME to VALUE in the environment and run COMMAND.
    
    Mandatory arguments to long options are mandatory for short options too.
      -i, --ignore-environment  start with an empty environment
      -0, --null           end each output line with NUL, not newline
      -u, --unset=NAME     remove variable from the environment
      -C, --chdir=DIR      change working directory to DIR
      -S, --split-string=S  process and split S into separate arguments;
                            used to pass multiple arguments on shebang lines
    

    所以,现在将 -S 传递给 env 即可完成这项工作

    【讨论】:

    • 这如何回答这个问题? OP想要一个相对于脚本位置的shebang;这些 shebangs 仍然与 pwd 位置相关。
    • @NathanArthur 它不是密码,它可能是脚本的相对路径,但肯定是脚本位置。下面的其他答案通过将这个想法扩展到路径中的多个参数和空格来支持这一点。通过使用#!/usr/bin/awk BEGIN{system("echo "ARGV[1])} 创建文件并从不同目录运行它来检查它
    【解决方案2】:

    扩展@Anton 的答案,以免在路径中的空格和其他特殊字符上失败,并解释更多关于魔法的信息。

    #!/bin/sh
    "true" '''\'
    exec "$(dirname "$(readlink -f "$0")")"/venv/bin/python "$0" "$@"
    '''
    
    __doc__ = """You will need to deliberately set your docstrings though"""
    
    print("This script is interpretable by python and sh!")
    

    这个聪明的脚本是 sh 和 python 都可以理解的。每个人对它的反应都不一样。 shebang 之后的前 2 行由 sh 解释,并使其将 exec 移交给相关的 python 二进制文件(提供相同的命令行参数)。这些相同的行被 python 安全地丢弃,因为它们相当于一个字符串(“true”),后跟一个多行字符串(''')。

    更多关于这个主题的信息可以阅读here

    【讨论】:

    • +1 提供了一个很好地解决符号链接的解决方案(我能识别出来自this answer? 的sn-p)并且在整个路径中都很健壮。这在我的系统上是一个插入式解决方案。
    【解决方案3】:

    看了这些答案后,我决定使用一个特定于 Python 的解决方案,它实际上不会改变 shebang。

    此解决方案使用系统 python 解释器来查找所需的解释器并执行它。这对我很有用,因为它允许我调整环境变量并确保正确的解释器。

    因为它使用 exec,所以它不会使内存使用加倍——新的解释器替换了最后一个。此外,退出状态和信号将得到正确处理。

    是一个需要在这个环境下运行的脚本加载的python模块。如果需要,导入此模块具有启动新解释器的副作用。通常,模块不应该有副作用,但替代方法是在执行非系统导入之前运行模块的功能,这将违反 PEP8。所以你必须选择你的毒药。

    """Ensure that the desired python environment is running."""
    
    
    import os
    import sys
    
    
    def ensure_interpreter():
        """Ensure we are running under the correct interpreter and environ."""
        abs_dir = os.path.dirname(os.path.abspath(__file__))
        project_root = os.path.normpath(os.path.join(abs_dir, '../../../'))
        desired_interpreter = os.path.join(project_root, 'bin/python')
    
        if os.path.abspath(sys.executable) == desired_interpreter:
            return
    
        env = dict(os.environ)
    
        def prefix_paths(key, prefix_paths):
            """Add prefix paths, relative to the project root."""
            new_paths = [os.path.join(project_root, p) for p in prefix_paths]
            new_paths.extend(env.get(key, '').split(':'))
            env[key] = ':'.join(new_paths)
    
        prefix_paths('PYTHONPATH', ['dir1', 'dir2'])
        prefix_paths('LD_LIBRARY_PATH', ['lib'])
        prefix_paths('PYTHON_EGG_CACHE', ['var/.python-eggs'])
        env['PROJECT_ROOT'] = project_root
        os.execvpe(desired_interpreter, [desired_interpreter] + sys.argv, env)
    
    
    ensure_interpreter()
    

    如果您不需要修改任何环境变量,您可以去掉env = dict(os.environ)os.execvpe(desired_interpreter, [desired_interpreter] + sys.argv, env) 之间的所有内容。

    【讨论】:

    • 非常有用,但缺点是第一个解释器需要是相同的版本。在我的情况下,python 3.4 将无法解析 f 字符串,即使它们进一步下降。
    • eddygeek,我认为要求是初始解释器与脚本中的任何语言结构兼容。您仍然可以使用较新的 stdlib 模块,并且您自己的模块可以使用与初始解释器不兼容的结构(假设在它们之前加载了 ensure_interpreter)。
    【解决方案4】:

    这是一个 shebang,它将传递参数,使用名称中包含非单词字符的脚本,以及使用旧版本的 perl(非破坏性替换标志 /r 直到 5.13 才添加.2)。已接受答案中的 perl 示例仅将脚本本身作为参数传递,而不是所有带有 @ARGV 的参数。

    #!/usr/bin/perl -e$_=$ARGV[0];s/[^\/]+$/python/;exec($_,@ARGV)
    

    这个 shebang 运行 perl 来运行一个内联脚本 -e。它将脚本$ARGV[0] 的路径分配给默认变量$_。然后在默认变量上执行 perl 替换 s/[^\/]+$/python/ 以将路径末尾的所有非正斜杠 / 字符 ([^\/]+) 替换为备用命令的名称运行(python)。最后,它使用exec($_ 运行新命令并将原始调用的所有参数传递给它(@ARGV

    【讨论】:

    • 这是用 perl v5.16.3 测试的
    【解决方案5】:

    对于在寻找 python hashbang 行的可移植解决方案时发现此问题并发现上面的 AWK 命令不适用于传递多个参数的其他人,请改用以下命令。

    #!/usr/bin/awk BEGIN{a=ARGV[1];b="";for(i=1;i<ARGC;i++){b=b" \""ARGV[i]"\"";}sub(/[a-z_.\-]+$/,"python",a);system(a"\t"b)}
    

    要更改当前目录中的所有脚本 hashbang 行,您可以执行以下操作。

    sed -i '1 s|^#!.*|#!/usr/bin/awk BEGIN{a=ARGV[1];b="";for(i=1;i<ARGC;i++){b=b" \""ARGV[i]"\"";}sub(/[a-z_.\-]+$/,"python3.5",a);system(a"\\t"b)}|' *
    

    【讨论】:

    • 我一直有一个未终止的 awk 异常,这解决了它。 #!/usr/bin/awk BEGIN{a=ARGV[1];b="";for(i=1;i&lt;ARGC;i++){b=b"\t"ARGV[i];}sub(/[a-z_.\-]+$/,"python",a);system(a""b)}
    • 确认@wizebin 的评论版本——效果很好!!!原始答案包含拼写错误(但无论如何谢谢!)。
    【解决方案6】:

    (由于缺少信用无法发表评论)

    Re bdrx 的回答 (https://stackoverflow.com/a/62268465/7379507):不幸的是,它对我不起作用。看起来@ARGV 是这里的罪魁祸首,忽略它确实有效(Perl v5.16.3):

    #!/usr/bin/perl -e$_=$ARGV[0];s/[^\/]+$/python/;exec("'$_'")                  
    

    当然,没有额外的参数。单引号对于保护目录或文件名中带有空格的路径是必要的。

    Re Kenneth E. Bellock 的回答 (https://stackoverflow.com/a/36160331/7379507) 并澄清 cmets:这也存在路径部分中的空白问题。

    这似乎(大部分)对我有用(GNU Awk 4.0.2):

    (请参阅下面的具有正确返回代码的更好变体 - 我将其保留用于教育目的)

    #!/usr/bin/awk BEGIN{a=ARGV[1];b="";for(i=2;i<ARGC;i++){b=b" "ARGV[i];}sub(/[^\/]+$/,"python",a);system(sprintf("'%s'%s",a,b))}                               
    

    修改:

    • 正则表达式替换除正斜杠以外的所有内容
    • 用空格引用目录和文件名的保护措施
    • 像 -v -i 这样的简单附加参数似乎可以工作,但不能像 -c'import sys' 这样引用参数
    • 我无法完全掌握 \t 的内容,因此以 i=2 开始循环并将 ARGV 条目与空格组合

    感谢您的启发。尽管如此,引用 shebang 对我来说仍然是某种谜...... ;-)

    编辑:刚刚注意到我不小心删除了“rc=system(..);exit rc”部分,因此丢失了 Python 进程返回代码。我不能让它工作我的修改。哎呀!

    EDIT2:好的,返回码的问题在于 shebang 行长度限制。但我意识到我可以不用 sprintf():

    !/usr/bin/awk BEGIN{a=ARGV[1];b="";for(i=2;i<ARGC;i++){b=b" "ARGV[i];}sub(/[^\/]+$/,"python",a);rc=system("'"a"'"b);exit rc}
    

    不过,引用参数的问题仍然存在。

    【讨论】:

    • 我在 rhel 7.9 上使用 perl v 5.16.3 测试了我的 shebang,其脚本在目录名称和脚本名称中都有一个空格。您遇到了什么问题或错误?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-07
    • 1970-01-01
    • 2013-10-06
    • 2011-10-13
    • 1970-01-01
    • 2015-09-08
    • 1970-01-01
    相关资源
    最近更新 更多