output the command line called by subprocess?
Asked Answered
A

5

66

I'm using the subprocess.Popen call, and in another question I found out that I had been misunderstanding how Python was generating arguments for the command line.

My Question
Is there a way to find out what the actual command line was?

Example Code :-

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

How would you write getCommandLine ?

Adest answered 12/2, 2013 at 16:19 Comment(0)
P
94

It depends on the version of Python you are using. In Python3.3, the arg is saved in proc.args:

proc = subprocess.Popen(....)
print("the commandline is {}".format(proc.args))

In Python2.7, the args not saved, it is just passed on to other functions like _execute_child. So, in that case, the best way to get the command line is to save it when you have it:

proc = subprocess.Popen(shlex.split(cmd))
print "the commandline is %s" % cmd

Note that if you have the list of arguments (such as the type of thing returned by shlex.split(cmd), then you can recover the command-line string, cmd using the undocumented function subprocess.list2cmdline:

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'
Plymouth answered 12/2, 2013 at 16:33 Comment(11)
I'm in 2.6. And in 2.6 at least it's good that list2cmdline is undocumented, because it doesn't work: for '--arg=foo bar' what python ACTUALLY does is '--arg="foo bar"' but what list2cmdline gives is '"--arg=foo bar"'... but thanks.Adest
I think the problem there is not with list2cmdline, but rather shlex.split. shlex.split('--arg="foo bar"') returns a list with a single item: ['--arg=foo bar'], while shlex.split('--arg = "foo bar"') correctly splits the arguments: ['--arg', '=', 'foo bar']. On this latter list list2cmdline works fine.Plymouth
By the way, Python2.6's subprocess module uses list2cmdline to convert args to a list whenever args is not a string so it is working in the sense that what you are seeing as the return value of list2cmdline(args) is exactly what is being passed on to execvp* or the Windows equivalent.Plymouth
When I pass "--arg=foo bar" to popen, it works (ie, it passes --arg="foo bar" to the program). When I pass the exact same string to list2cmdline, it gives "--arg=foo bar" (with quotation marks)Adest
I'm confused about what you are actually passing to list2cmdline. It expects a list not a string.Plymouth
Sorry, set cmdline = ["cmd", "--arg=foo bar", "baz"] I then run popen with cmdline, and get cmd --arg="foo bar" baz When I call list2cmdline(cmdline) I get cmd "--arg=foo bar" bazAdest
Are you saying that when you use subprocess.Popen('cmd "--arg=foo bar" baz', shell=True) it works, but when you use subprocess.Popen(shlex.split('cmd "--arg=foo bar" baz')) (without shell=True) it fails?Plymouth
No. I have cmdline as a list, as above. I call subprocess.Popen(cmdline) and it works. When I call list2cmdline(cmdline) it gives a commandline that will not work.Adest
Could you post a runnable example? (I am unable to reproduce this problem.)Plymouth
Well I'd need to write a program that prints its arguments out, otherwise you don't see what the program is actually GETTING... Let me see what I can whip upAdest
AHA. So the issue is that my program is adding the quotes. the thing that was confusing to me was that Popen never actually CREATES a command line. Everything is done through IPC, therefore if one of the arguments in the list is '--arg=foo bar' then that's fine, that's an element in the list. there is no need for quotes. Then on the RECEIVING program's end, it adds the quotes because it needs them for other reasons, and it expects the shell to have stripped off the ones that were there initially...Adest
A
7

The correct answer to my question is actually that there IS no command line. The point of subprocess is that it does everything through IPC. The list2cmdline does as close as can be expected, but in reality the best thing to do is look at the "args" list, and just know that that will be argv in the called program.

Adest answered 12/2, 2013 at 20:26 Comment(1)
list2cmdline() is useful only on Windows for applications compatible with how MS C runtime parses command-line. It converts a list of arguments to a string that is passed to CreateProcess(). cmd.exe uses different rules. On POSIX the list is passed directly to os.execve() (+/- os.fsencode()).Superfamily
Y
1

Beautiful and scalable method

I have been using something like this:

#!/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']
)

Sample run:

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

Output:

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

Feature summary:

  • makes huge command lines readable with one option per line
  • add a + to commands like sh -x so users can differentiate commands from their output easily
  • show cd, and extra environment variables if they are given to the command. These only printed if given, generating a minimal shell command.

All of this allows users to easily copy the commands manually to run them if something fails, or to see what is going on.

Tested on Python 3.5.2, Ubuntu 16.04. GitHub upstream.

Yep answered 16/10, 2018 at 13:48 Comment(0)
A
0

You can see it by passing the process id to ps command, if you are on POSIX OS:

import subprocess

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

Output (see the CMD column):

  PID TTY           TIME CMD
 7778 ttys004    0:00.01 ls -la
Ayurveda answered 7/7, 2022 at 4:19 Comment(0)
S
0

On windows, I used @catwith 's trick (thanks, btw):

wmic process where "name like '%mycmd%'" get processid,commandline

where "mycmd" is a part of the cmd unique to your command (used to filter irrelevant system commands)

That's how I revealed another bug in the suprocess vs windows saga. One of the arguments I had had its double-quotes escaped a-la unix! \"asdasd\"

Shep answered 26/10, 2022 at 17:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.