恢复原始 argv

Posted

技术标签:

【中文标题】恢复原始 argv【英文标题】:Recovering original argv 【发布时间】:2018-08-31 22:26:49 【问题描述】:

当使用python 显式调用脚本时,argv 被混淆,因此argv[0] 是正在运行的脚本的路径。如果以python foo/bar.py 甚至python -m foo.bar 调用,就会出现这种情况。

我需要一种方法来恢复原始的argv(即python 收到的那个)。不幸的是,这并不像在sys.argv 前添加sys.executable 那样简单,因为python foo/bar.pypython -m foo.bar 不同(隐含的PYTHONPATH 不同,这取决于您的模块结构可能很重要)。

更具体地说,在python foo/bar.py some other argspython -m foo.bar some other args 的情况下,我希望分别恢复['python', 'foo/bar.py', 'some', 'other', 'args']['python', '-m', 'foo.bar', 'some', 'other', 'args']

我知道之前有关于此的问题:

how to get the ORIGINAL command line in python? with spaces, tabs, etc Full command line as it was typed

但这些似乎对 shell 的工作方式有误解,答案反映了这一点。我对撤消 shell 的工作不感兴趣(例如,评估的 shell 变量和函数都很好),我只想得到原始的 argvpython

我发现的only solution是使用/proc/<PID>/cmdline

import os
with open("/proc//cmdline".format(os.getpid()), 'rb') as f:
  original_argv = f.read().split('\0')[:-1]

这确实有效,但它仅适用于 Linux(没有 OSX,而​​且 Windows 支持似乎需要安装 wmi package)。幸运的是,对于我当前的用例,这个限制很好。但是,如果有一个更干净、跨平台的方法,那就太好了。

/proc/<PID>/cmdline 方法有效的事实让我希望 python 在运行脚本之前不会执行(至少不是系统调用 exec,但可能是 exec 内置)。我记得在某处读到,所有这些参数处理(例如-m)都是在纯 Python 中完成的,而不是 C(这可以通过 python -m this.does.not.exist 产生一个看起来像是来自运行时的异常的事实得到证实)。所以,我敢猜测,在纯 python 中的某个地方,原始的 argv 是可用的(也许这需要通过运行时初始化进行一些探索?)。

tl;dr 是否有一种跨平台(最好是内置的)方法来获取传递给 python 的原始 argv(在它删除 python 可执行文件并转换 @987654358 之前@变成blah.py)?

edit 从 spelunking 中,我发现了 Py_GetArgcArgv,它可以通过 ctypes 访问(找到它 here,链接到 several SO 提到这种方法的帖子):

import ctypes

_argv = ctypes.POINTER(ctypes.c_wchar_p)()
_argc = ctypes.c_int()

ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(_argc),
                                ctypes.byref(_argv))

argv = _argv[:_argc.value]
print(argv)

现在这是 OS 可移植的,但不是 python 实现可移植的(仅适用于 cpython,ctypes 如果你不需要它,那就太糟糕了)。另外,特别是,我在 Ubunutu 16.04 上没有得到正确的输出(python -m foo.bar 给了我['python', '-m', '-m']),但我可能只是犯了一个愚蠢的错误(我在 OSX 上得到了相同的行为)。拥有一个完全可移植的解决方案会很棒(不会深入到ctypes)。

【问题讨论】:

相关:***.com/q/44862323/7051394 如何为 python 创建一个包装 C 程序,它将参数存储在一个文件中,并将文件名作为 env 传递。变量到 python 读取? (也称为python,它是一个包装器)。丑陋但可以工作,而且便携。 我的意思是:调用包装器“python”并将其放在路径 before 您的原始 python 中(通过在路径中的第二个来从包装器中找到它)。我承认这不是最优的。可以打开 PEP 来请求诸如 sys.original_argv 之类的功能。 请问获得原始 argv 背后的基本原理是什么?你的最终目标是什么?或许知道这一点,SO 的人或许能够提供帮助。 @HaiVu 基本原理是在随机/非确定性测试的背景下(在运行所有测试之前选择了一些随机种子),我想打印出一条关于测试失败的有用错误消息,例如: Randomized test failed. Run this to reproduce: SEED=123 python -m however.tests.were.run.before。这里的想法是您可以直接复制并粘贴该命令以使用相同的种子重新运行。随机的东西是微不足道的,但我需要能够获得原始的 argv 以产生可以复制和粘贴然后运行的东西。 (rspec 做这样的事情) 【参考方案1】:

您所说的问题是:

    用户使用环境变量和参数调用了我的应用程序。 我想显示一个“像这样运行”的诊断信息,它可以准确地重现当前运行的结果。

至少有两种解决方案:

    放弃“复制”方面,因为原始的 bash 调用命令丢失到可移植的 python 应用程序中,而是去“相同的效果”。 按照 Jean-François Fabre 的建议,使用包装器来捕获原始调用命令。

使用 (1) 你会愿意接受 ['-m', 'foo'] 变成 ['foo.py'],甚至把它变成 ['/some/dir/foo.py'] case PYTHONPATH 可能会导致麻烦。将 ['a', 'b c'] 显示为 "a" "b c",或更简洁地显示为 a "b c",很简单。如果 SEED 等环境变量是命令行界面的重要组成部分,那么您还需要遍历 envp 并输出它们。为了实现真正的可重复性,您可以选择将输入 args 转换为规范形式,与观察到的输入 args 进行比较,如果它们不相同,则使用规范形式执行,因此无法使用“奇数”语法执行大部分代码.

使用 (2),您可以将应用程序隐藏在一些不方便命名的文件中,广泛宣传包装程序,并享受在 args 被删除之前看到它们的好处。

【讨论】:

【参考方案2】:

这似乎是 XY 问题,而您为了适应一些现有的复杂测试设置而陷入困境(我在您的 comment 中找到了问题背后的问题)。进一步的努力最好花在编写一个健全的测试设置上。

    使用better test runner,而不是单元测试。 在测试设置中创建任何初始状态,而不是在进入 Python 运行时之前的外部环境中。 使用插件进行随机化和种子处理,我个人使用this one,但还有其他的。

例如,如果您决定使用 pytest 运行程序,则可以在 setup.cfg 文件的 [tool:pytest] 部分和/或 fixture 设置 (conftest.py) 中配置所有测试设置。可以使用环境变量和/或命令行参数来覆盖默认测试配置,这些方法都不会被 shell 或 Python 解释器启动期间弄乱。

执行测试套件的方式可以而且应该像执行单个命令一样简单:

pytest

然后您认为需要恢复原始sys.argv 的问题就会消失。

【讨论】:

如果您从问题中删除 XY 问题,您仍然会遇到恢复原始 argv 的有效问题,例如修改 LD_LIBRARY_PATH 所需的(参见 ***.com/questions/23244418/…)跨度>

以上是关于恢复原始 argv的主要内容,如果未能解决你的问题,请参考以下文章

演员 - 临时流中断 - 恢复原始

如何将 Android Studio Live 模板恢复为原始模板?

从多个水印中恢复原始图像

抖音号怎么恢复原始纯数字号码?

类内声明的字符串不断恢复为原始值

将视图恢复到原始 xml 状态