输出由子进程调用的命令行?

63

我正在使用 subprocess.Popen 调用,在另一个问题中,我发现自己误解了 Python 如何为命令行生成参数。

我的问题
有没有办法找出实际的命令行是什么?

示例代码:

proc = subprocess.popen(....)
print "the commandline is %s" % proc.getCommandLine()

你该如何编写getCommandLine函数?

5个回答

92
这取决于您使用的Python版本。在Python3.3中,参数保存在proc.args中:(参见链接)
proc = subprocess.Popen(....)
print("the commandline is {}".format(proc.args))
在Python2.7中,并未保存args,它只是传递给其他函数,如_execute_child。因此,在这种情况下,获取命令行的最佳方法是在拥有它时将其保存:
proc = subprocess.Popen(shlex.split(cmd))
print "the commandline is %s" % cmd
请注意,如果您有参数列表(例如由shlex.split(cmd)返回的内容),则可以使用未记录的函数subprocess.list2cmdline恢复命令行字符串cmd
In [14]: import subprocess

In [15]: import shlex

In [16]: cmd = 'foo -a -b --bar baz'

In [17]: shlex.split(cmd)
Out[17]: ['foo', '-a', '-b', '--bar', 'baz']

In [18]: subprocess.list2cmdline(['foo', '-a', '-b', '--bar', 'baz'])
Out[19]: 'foo -a -b --bar baz'

我在2.6版本中。至少在2.6版本中,list2cmdline没有文档是好事,因为它不起作用:对于'--arg=foo bar',Python实际上执行的是'--arg="foo bar"',但list2cmdline给出的是'"--arg=foo bar"'... 不过还是谢谢。 - Brian Postow
当我将"--arg=foo bar"传递给popen时,它可以正常工作(即,它将--arg="foo bar"传递给程序)。但是,当我将完全相同的字符串传递给list2cmdline时,它会返回带有引号的"--arg=foo bar"。 - Brian Postow
我对你实际传递给 list2cmdline 的内容感到困惑。它期望的是一个列表而不是一个字符串。 - unutbu
不。我有一个像上面那样的cmdline列表。我调用subprocess.Popen(cmdline),它可以工作。当我调用list2cmdline(cmdline)时,它会给出一个无法工作的命令行。 - Brian Postow
1
AHA。问题在于我的程序添加了引号。让我困惑的是,Popen从未实际创建命令行。一切都通过IPC完成,因此,如果列表中的一个参数是'--arg=foo bar',那么没关系,那是列表中的一个元素。不需要引号。然后,在接收程序的端口,它添加引号,因为它需要它们出于其他原因,并且它期望shell已经剥离了最初存在的引号... - Brian Postow
显示剩余6条评论

7

我的问题的正确答案实际上是没有命令行。subprocess的重点在于它通过IPC执行所有操作。list2cmdline尽可能地接近,但实际上最好的方法是查看“args”列表,并知道这将成为被调用程序中的argv。


4
list2cmdline()函数仅适用于兼容MS C运行时解析命令行的Windows应用程序。它将参数列表转换为传递给CreateProcess()的字符串。在POSIX系统中,参数列表会直接传递给os.execve()(+/- os.fsencode())。请注意,cmd.exe使用不同的规则。 - jfs

2

美观且可扩展的方法

我一直在使用类似于这样的东西:

#!/usr/bin/env python3

import os
import shlex
import subprocess
import sys

def run_cmd(cmd, cwd=None, extra_env=None, extra_paths=None, dry_run=False):
    if extra_env is None:
        extra_env = {}
    newline_separator = ' \\\n'
    out = []
    kwargs = {}
    env = os.environ.copy()

    # cwd
    if 'cwd' is not None:
        kwargs['cwd'] = cwd

    # extra_env
    env.update(extra_env)
    for key in extra_env:
        out.append('{}={}'.format(shlex.quote(key), shlex.quote(extra_env[key])) + newline_separator)

    # extra_paths
    if extra_paths is not None:
        path = ':'.join(extra_paths)
        if 'PATH' in env:
            path += ':' + env['PATH']
        env['PATH'] = path
        out.append('PATH="{}:${{PATH}}"'.format(':'.join(extra_paths)) + newline_separator)

    # Command itself.
    for arg in cmd:
        out.append(shlex.quote(arg) + newline_separator)

    # Print and run.
    kwargs['env'] = env
    print('+ ' + '  '.join(out) + ';')
    if not dry_run:
        subprocess.check_call(cmd, **kwargs)

run_cmd(
    sys.argv[1:],
    cwd='/bin',
    extra_env={'ASDF': 'QW ER'},
    extra_paths=['/some/path1', '/some/path2']
)

示例运行:

./a.py echo 'a b' 'c d' 

输出:

+ ASDF='QW ER' \
  PATH="/some/path1:/some/path2:${PATH}" \
  echo \
  'a b' \
  'c d' \
;
a b c d

特性摘要:

  • 每个选项只占一行,使庞大的命令行易读
  • 在类似sh -x的命令中添加+,以便用户轻易区分命令和命令输出
  • 如果命令中给出了cd或其他额外的环境变量,就显示它们。只有在这些变量被给出时才会打印,生成最小化的shell命令。

所有这些都可以使用户手动复制命令来运行命令,以避免出现错误或查看正在进行的操作。

在Python 3.5.2和Ubuntu 16.04上测试通过。具体细节请参考GitHub源代码


0
在Windows上,我使用了@catwith的技巧(感谢,顺便说一下):
wmic process where "name like '%mycmd%'" get processid,commandline 其中“mycmd”是您命令中唯一的cmd部分(用于过滤不相关的系统命令)
这就是我发现suprocess和Windows之间另一个错误的方式。 我其中一个参数已经用Unix风格转义了双引号! \"asdasd\"

0

如果你在POSIX操作系统上,可以通过将进程ID传递给ps命令来查看它:

import subprocess

proc = subprocess.Popen(["ls", "-la"])
subprocess.Popen(["ps", "-p", str(proc.pid)])

输出(请参见CMD列):

  PID TTY           TIME CMD
 7778 ttys004    0:00.01 ls -la

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接