Skip to content

Always use a sequence in args to subprocess.Popen() #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

fridex
Copy link
Contributor

@fridex fridex commented Apr 16, 2018

According to docs, it is recommended to pass args as a sequence.

According to docs, it is recommended to pass args as a sequence.
@fridex
Copy link
Contributor Author

fridex commented Apr 16, 2018

Without this fix I'm experiencing the following issue:

In [1]: import delegator

In [2]: delegator.run(['echo', 'hello']).out
Out[2]: '\n'

In [3]: 

According to docs (see args section):

Unless otherwise stated, it is recommended to pass args as a sequence. On POSIX, if args is a string, the string is interpreted as the name or path of the program to execute. However, this can only be done if not passing arguments to the program.

@fridex
Copy link
Contributor Author

fridex commented Apr 23, 2018

ping @kennethreitz, any change to get this reviewed/in? thanks!

Copy link

@akshaykumar90 akshaykumar90 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason your code example above doesn't work is the shell argument being set to True in _default_popen_kwargs. Further below in the docs:

The shell argument (which defaults to False) specifies whether to use the shell as the program to execute. If shell is True, it is recommended to pass args as a string rather than as a sequence.

This is because

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.

This translates to the following code in your example, and a bare echo is run which correctly returns '\n'.

Popen(['/bin/sh', '-c', 'echo', 'hello', ...])

@@ -182,7 +182,11 @@ def run(self, block=True, binary=False, cwd=None, env=None):
popen_kwargs['cwd'] = cwd
if env:
popen_kwargs['env'].update(env)
s = subprocess.Popen(self._popen_args, **popen_kwargs)
args = self._popen_args
if not isinstance(args, str):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to account for unicode string type in PY2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If python2 is still relevant, are you fine with introducing six as a new dependency and use six.string_types?

args = self._popen_args
if not isinstance(args, str):
# It is recommended to pass a sequence based on docs.
args = subprocess.list2cmdline(args)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subprocess.list2cmdline is used mostly (only?) on Windows, so using it here might have unexpected results on *nix systems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative could be to use shlex here, but required routines are only Python3.3+?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to set shell kwargs depending on what the given args are.
Using shlex and/or subprocess.list2cmdline doesn't work in all cases, I guess.

More over shell=True should only be used if the cmd line can't be represented with a sequence.

s = subprocess.Popen(self._popen_args, **popen_kwargs)
args = self._popen_args
if not isinstance(args, str):
# It is recommended to pass a sequence based on docs.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment, although correct, is confusing in this context since we're converting from sequence to string on the next line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove it.

@timofurrer
Copy link
Contributor

@fridex do you mind resolving the merge conflicts to get a step further to know if this can/should be merged?

@fridex
Copy link
Contributor Author

fridex commented Feb 14, 2019

@timofurrer done, thanks for refresh.

@timofurrer
Copy link
Contributor

Awesome, @fridex - thanks.
Can you have a look at the feedback from @akshaykumar90.

@fridex
Copy link
Contributor Author

fridex commented Feb 15, 2019

The reason your code example above doesn't work is the shell argument being set to True in _default_popen_kwargs. Further below in the docs:

The shell argument (which defaults to False) specifies whether to use the shell as the program to execute. If shell is True, it is recommended to pass args as a string rather than as a sequence.

This is because

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.

This translates to the following code in your example, and a bare echo is run which correctly returns '\n'.

Popen(['/bin/sh', '-c', 'echo', 'hello', ...])

That's is true - but this is based on the internal behavior relying on how delegator.py is implemented. Following my example:

In [1]: import delegator

In [2]: delegator.run(['echo', 'hello']).out
Out[2]: '\n'

I would expect "hello" to be printed relying solely on delegator's API (as a consumer of delegator library).

args = self._popen_args
if not isinstance(args, str):
# It is recommended to pass a sequence based on docs.
args = subprocess.list2cmdline(args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to set shell kwargs depending on what the given args are.
Using shlex and/or subprocess.list2cmdline doesn't work in all cases, I guess.

More over shell=True should only be used if the cmd line can't be represented with a sequence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants