Bruce
Bruce

Reputation: 1380

Python subproces.call not working as expected

I can not get the subprocess.call() to work properly:

>>> from subprocess import call
>>> call(['adduser', '--home=/var/www/myusername/', '--gecos', 'GECOS', '--disabled-login', 'myusername'], shell=True)
adduser: Only one or two names allowed.
1

But without shell=True:

>>> call(['adduser', '--home=/var/www/myusername/', '--gecos', 'GECOS', '--disabled-login', 'myusername'])
Adding user `myusername' ...
Adding new group `myusername' (1001) ...
Adding new user `myusername' (1001) with group `myusername' ...
Creating home directory `/var/www/myusername/' ...
Copying files from `/etc/skel' ...
0

Or the same directly in shell:

root@www1:~# adduser --home=/var/www/myusername/ --gecos GECOS --disabled-login myusername
Adding user `myusername' ...
Adding new group `myusername' (1001) ...
Adding new user `myusername' (1001) with group `myusername' ...
Creating home directory `/var/www/myusername/' ...
Copying files from `/etc/skel' ...

I miss some logic in the shell=True behavior. Can somebody explain me why? What is wrong with the first example? From the adduser command error message it seems that arguments are somehow crippled.

Thanks!

Upvotes: 0

Views: 518

Answers (4)

aychedee
aychedee

Reputation: 25619

When you specify shell=True you switch to quite different behaviour. From the docs:

On Unix with shell=True, the shell defaults to /bin/sh. If args is a string, the string specifies the command to execute through the shell. This means that the string must be formatted exactly as it would be when typed at the shell prompt. This includes, for example, quoting or backslash escaping filenames with spaces in them. If args is a sequence, the first item specifies the command string, and any additional items will be treated as additional arguments to the shell itself. That is to say, Popen does the equivalent of:

Popen(['/bin/sh', '-c', args[0], args[1], ...])

So you are running the equivalent of

/bin/sh -c "adduser" --home=/var/www/myusername/ --gecos GECOS --disabled-login myusername

The error message you are getting is what happens when you try and run adduser without any arguments as all the arguments are being passed to sh.

If you want to set shell=True then you would need to call it like this:

call('adduser --home=/var/www/myusername/ --gecos GECOS --disabled-login myusername', shell=True)

OR like this:

call(['adduser --home=/var/www/myusername/ --gecos GECOS --disabled-login myusername'], shell=True)

But mostly you just want to use call without the shell=True and use a list of arguments. As per your second, working, example.

Upvotes: 2

dawe
dawe

Reputation: 473

If shell is True the specified command will be executed through the shell, that is the shell takes care of filename wildcards, environment variable expansion etc. When you use shell=True the cmd is a single string, it must be formatted exactly as it would be typed in the shell. If shell=True and cmd is a sequence, the first argument specifies the command and the additional arguments are treated as arguments to the shell itself (by the -c switch).

If shell=False, and a sequence of arguments is provided the module will take care of properly escaping and quoting the arguments and for example ~ won't be expanded as the home directory etc.

Read more about it in the subprocess documentation, and mind the security hazard related to shell=True.

Upvotes: 0

kmerenkov
kmerenkov

Reputation: 2889

It seems that with shell=True you need to pass string into args rather than list of arguments.

A simple test:

In [4]: subprocess.call(['echo', 'foo', 'bar'], shell=True)

Out[4]: 0

In [5]: subprocess.call('echo foo bar', shell=True)
foo bar
Out[5]: 0

I.e. echo got the right arguments only when I used string, not list.

Python version 2.7.3

Upvotes: 0

isedev
isedev

Reputation: 19641

I am not 100% sure about this but I think that it you specify Shell=True, you should be passing the command line as a single string which the shell itself will interpret:

>>> call('adduser --home=/var/www/myusername/ --gecos GECOS --disabled-login myusername', shell=True)

Upvotes: 1

Related Questions