【问题标题】:python: obtaining the OS's argv[0], not sys.argv[0]python:获取操作系统的 argv[0],而不是 sys.argv[0]
【发布时间】:2019-12-15 03:18:17
【问题描述】:

(有人问过这个问题here,但答案是特定于 Linux 的;我在 FreeBSD 和 NetBSD 系统上运行(EDIT:通常)没有/proc。)

Python 似乎使argv[0] 变得笨拙,因此您不会像 C 程序那样得到传递给进程的内容。公平地说,sh、bash 和 Perl 也好不到哪里去。有什么办法可以解决这个问题,所以我的 Python 程序可以获得原始值吗?我在这个 FreeBSD 系统上拥有管理权限,并且可以做一些事情,比如更改每个人的默认 PATH 环境变量以指向包含 python2 和 python3 的目录之前的某个其他目录,但我无法控制创建 /proc。我有一个说明问题的脚本。一、脚本的输出:

the C child program gets it right: arbitrary-arg0 arbitrary-arg1
the python2 program dumbs it down: ['./something2.py', 'arbitrary-arg1']
the python3 program dumbs it down: ['./something3.py', 'arbitrary-arg1']
the sh script       dumbs it down: ./shscript.sh arbitrary-arg1
the bash script     dumbs it down: ./bashscript.sh arbitrary-arg1
the perl script drops arg0:        ./something.pl arbitrary-arg1

...现在是脚本:

#!/bin/sh

set -e
rm -rf work
mkdir work
cd work
cat > childc.c << EOD; cc childc.c -o childc
#include <stdio.h>
int main(int    argc,
         char **argv
        )
{
  printf("the C child program gets it right: ");
  printf("%s %s\n",argv[0],argv[1]);
}
EOD
cat > something2.py <<EOD; chmod 700 something2.py
#!/usr/bin/env python2
import sys
print "the python2 program dumbs it down:", sys.argv
EOD
cat > something3.py <<EOD; chmod 700 something3.py
#!/usr/bin/env python3
import sys
print("the python3 program dumbs it down:", sys.argv)
EOD
cat > shscript.sh <<EOD; chmod 700 shscript.sh
#!/bin/sh
echo "the sh script       dumbs it down:" \$0 \$1
EOD
cat > bashscript.sh <<EOD; chmod 700 bashscript.sh
#!/bin/sh
echo "the bash script     dumbs it down:" \$0 \$1
EOD
cat > something.pl <<EOD; chmod 700 something.pl
#!/usr/bin/env perl
print("the perl script drops arg0:        \$0 \$ARGV[0]\n")
EOD
cat > launch.c << EOD; cc launch.c -o launch; launch
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int    argc,
         char **argv,
         char **arge)
{
  int    child_status;
  size_t program_index;
  pid_t  child_pid;

  char  *program_list[]={"./childc",
                         "./something2.py",
                         "./something3.py",
                         "./shscript.sh",
                         "./bashscript.sh",
                         "./something.pl",
                         NULL
                        };

  char  *some_args[]={"arbitrary-arg0","arbitrary-arg1",NULL};

  for(program_index=0;
      program_list[program_index];
      program_index++
     )
  {
    child_pid=fork();

    if(child_pid<0)
    {
      perror("fork()");
      exit(1);
    }
    if(child_pid==0)
    {
      execve(program_list[program_index],some_args,arge);
      perror("execve");
      exit(1);
    }
    wait(&child_status);
  }

  return 0;
}
EOD

【问题讨论】:

  • 只是好奇,它的用例是什么?
  • (a) 我在条目顶部链接的另一个 stackoverflow 项目本身指向一个引发相同问题的非 stackoverflow 项目,并声称 CUPS 实际上使用 argv[0 ] 包含一个 URL! (b) 这很复杂,但我会有一个目录树,其中每个都有同名的 python 程序,每个人都想知道用户是如何到达那里的。
  • 这与表现不佳的脚本语言关系不大,而与 shebang 机制及其工作方式有关。一些细节是here。记下底部的表格......在许多情况下,跨操作系统的行为非常不同。 CUPS 的情况很糟糕……你可以看看DEVICE_URI 环境变量吗?
  • @JohnSzakmeister:感谢您的链接,最有启发性。是的,这是一件大事(至少);我的答案中的演示脚本显示了如何使用一些脚本语言来克服这个问题,也许其他人可以添加到列表中。 CUPS 的情况很丑陋(我想,这让我原来的问题有点丑陋)。但由于 C 程序可以处理任意 argv[0],因此没有理由不将其扩展到 shebang 情况。我既不维护也不使用 CUPS,但认为 URL 的使用是......可笑的。

标签: python


【解决方案1】:

以下是对我想要要问的问题的普遍有用的答案。

kabanus 给出的答案非常好,考虑到我表达问题的方式,所以他当然得到了向上箭头和复选标记。在我看来,透明度是一个美丽的加分项。

但事实证明,我并没有完全说明情况。每个 python 脚本都以 shebang 开头,shebang 功能使得启动带有人工 argv[0] 的 python 脚本变得更加复杂。

另外,透明不是我的目标;向后兼容是。我希望正常情况下 sys.argv 可以在出厂时按原样工作,无需我的修改即可开箱即用。另外,我希望任何启动带有人工 argv[0] 的 python 脚本的程序都不必担心任何额外的参数操作。

部分问题是克服“shebang 改变 argv”问题。

答案是用 C 语言为每个脚本编写一个包装器,然后启动程序会启动该程序而不是实际的脚本。实际的脚本会查看父进程(包装器)的参数。

很酷的是,这可以用于除 python 之外的脚本类型。您可以下载概念证明here,它演示了适用于 python2、python3、sh、bash 和 perl 的解决方案。您必须使用 dos2unix 或 fromdos 将每个 CRLF 更改为 LF。这是 python3 脚本处理它的方式:

def get_arg0():
    return subprocess.run("ps -p %s -o 'args='" % os.getppid(),
                          shell=True,
                          stdout=subprocess.PIPE,
                          stderr=subprocess.PIPE
                         ).stdout.decode(encoding='latin1').split(sep=" ")[0]

该解决方案不依赖 /proc,因此它可以在 FreeBSD 和 Linux 上运行。

【讨论】:

    【解决方案2】:

    我认为这里阻力最小的路径有点老套,但可能适用于任何操作系统。基本上,您将 Python 调用双重包装。首先(以 Python 3 为例),将路径中的 Python3 替换为一个小的 C 程序,您知道该程序可以信任:

    #include<stdlib.h>
    #include<string.h>
    int main(int argc, char **argv) {
        // The python 3 below should be replaced by the path to the original one
        // In my tests I named this program wrap_python so there was no problem
        // but if you are changing this system wide (and calling the wrapper python3
        //  you can't leave this.
        const char *const program = "python3 wrap_python.py";
        size_t size = strlen(program) + 1; // Already added null character at end
        for(int count = 0; count < argc; ++count)
            size += strlen(argv[count]) + 1; // + 1 for space
    
        char *cmd = malloc(size);
        if(!cmd) exit(-1);
        cmd[0] = '\0';
        strcat(cmd, program);
        for(int count = 1; count < argc; ++count) {
            strcat(cmd, " ");
            strcat(cmd, argv[count]);
        }
        strcat(cmd, " ");
        strcat(cmd, argv[0]);
        return system(cmd);
    }
    

    您可以使这更快,但是,嘿,过早的优化?

    请注意,我们正在调用一个名为 wrap_python.py 的脚本(您可能需要在这里提供完整路径)。我们想要传递“真实的”argv,但我们需要在 Python 上下文中进行一些操作以使其透明。真正的argv[0] 作为最后一个参数传递,wrap_python.py 是:

    from sys import argv
    argv[0] = argv.pop(-1)
    print("Passing:", argv) # Delete me
    exit(exec(open(argv[1]).read())) # Different in Python 2. Close the file handle if you're pedantic.
    

    我们的小型包装器将 argv[0] 替换为 C 包装器提供的包装器,将其从末尾移除,然后在相同的上下文中手动执行。特别是__name__ == __main__ 是真的。

    这将运行为

    python3 my_python_script arg1 arg2 etc...
    

    您的路径现在将指向该原始 C 程序。对此进行测试

    import sys
    print(__name__)
    print("Got", sys.argv)
    

    产量

    __main__
    Got ['./wrap_python', 'test.py', 'hello', 'world', 'this', '1', '2', 'sad']
    

    请注意,我将我的程序称为 wrap_python - 你想将其命名为 python3

    【讨论】:

    • 鉴于我对问题的表述方式,您的解决方案非常出色,并且增加了交易的透明度。事实证明,我没有完全描述这种情况。我发布了一个不同的答案来解决这种情况;也许有人可以利用它。
    • @BillEvansatMariposa 谢谢,我现在明白你的意思了,你的回答很棒。关于shebang的好点(用于特殊处理),我将不得不考虑这是否可以检测到,因为现在我很感兴趣!
    • shebang 的存在是可检测的:使用 open(sys.argv[0]) as phyle: xxx=phyle.readline() 会做到。我无法弄清楚程序如何检测如果它以 shebang 开头,命令行是否只是程序名称,或者它是否是“python3”(或变体)后跟程序名称。检查该进程或其父进程的“ps”行似乎没有帮助。
    【解决方案3】:

    使用 Python 的 ctypes 模块获取默认设置为 argv[0] 的“程序名称”。 See Python source code here。例如:

    import ctypes
    
    GetProgramName = ctypes.pythonapi.Py_GetProgramName
    GetProgramName.restype = ctypes.c_wchar_p
    
    def main():
        print(GetProgramName())
    
    if __name__ == '__main__':
        main()
    

    运行命令打印:

    $ exec -a hello python3 name.py 
    hello
    

    【讨论】:

    • 这给出了程序名称,你是对的。不过,我正在寻求对任意 sys.argv[0] 的访问,它可能与程序名称不同。只是为了好玩儿,我把你的语句(在最后的语句周围有一个“打印”函数调用)放在一个脚本中并测试它。任意的 sys.argv[0] 没有通过。
    • @BillEvansatMariposa 默认情况下,python 中的程序名称设置为 argv[0]。
    • “设置为”可能意味着程序名称被复制到 argv[0] 中,或者程序名称被设置为 argv[0] 中的任何内容;换句话说,两个相反的意思。这两个中的第一个是正确的。这里的任务是克服默认设置,让 python 脚本可以访问在调用进程对 execve(2) 的调用中任意放置在 argv[0] 中的任何内容。而且,如您所见,我的回答满足了这一要求。
    • 你的例子教育了我,谢谢!但它不适用于开头的 shebang #!/usr/bin/env python3。
    • 更正:如果您直接运行该脚本,则它不适用于开头的 shebang #!/usr/bin/env python3,而命令行中没有“python3”。
    猜你喜欢
    • 2011-06-19
    • 2010-09-21
    • 2019-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-02
    • 2013-09-11
    • 1970-01-01
    相关资源
    最近更新 更多