【发布时间】:2013-11-20 11:58:16
【问题描述】:
我尝试构建可以随时随地使用的脚本。为此,我使用了一个定制的 python,它始终位于相对于脚本的父目录中。
这样我可以将我的包加载到 U 盘上,它可以在任何地方工作,无论 U 盘安装在哪里以及是否安装了 python。
但是,当我使用
#!../python
那么它只有在脚本从其目录中被调用时才起作用,这当然是不可接受的。
有没有办法做到这一点,或者在当前的 shebang 机制中这是不可能的?
【问题讨论】:
我尝试构建可以随时随地使用的脚本。为此,我使用了一个定制的 python,它始终位于相对于脚本的父目录中。
这样我可以将我的包加载到 U 盘上,它可以在任何地方工作,无论 U 盘安装在哪里以及是否安装了 python。
但是,当我使用
#!../python
那么它只有在脚本从其目录中被调用时才起作用,这当然是不可接受的。
有没有办法做到这一点,或者在当前的 shebang 机制中这是不可能的?
【问题讨论】:
this page 上有一组健康的多行 shebang 脚本,适用于多种语言,例如:
#!/bin/sh
"exec" "`dirname $0`/python" "$0" "$@"
print copyright
如果您想要单行 shebang,this answer(和问题)会详细解释问题并建议使用 shebang 中的其他脚本的以下方法:
#!/usr/bin/awk BEGIN{a=ARGV[1];sub(/[a-z_.]+$/,"python",a);system(a"\t"ARGV[1])}
#!/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 即可完成这项工作
【讨论】:
#!/usr/bin/awk BEGIN{system("echo "ARGV[1])} 创建文件并从不同目录运行它来检查它
扩展@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。
【讨论】:
看了这些答案后,我决定使用一个特定于 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) 之间的所有内容。
【讨论】:
这是一个 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)
【讨论】:
对于在寻找 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)}|' *
【讨论】:
#!/usr/bin/awk BEGIN{a=ARGV[1];b="";for(i=1;i<ARGC;i++){b=b"\t"ARGV[i];}sub(/[a-z_.\-]+$/,"python",a);system(a""b)}
(由于缺少信用无法发表评论)
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))}
修改:
感谢您的启发。尽管如此,引用 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}
不过,引用参数的问题仍然存在。
【讨论】: