Sanitising arguments to shell commands (was: Waiting for a subprocess to exit)

Ben Finney ben+python at benfinney.id.au
Fri Aug 21 05:08:08 EDT 2009


Miles Kaufmann <milesck at umich.edu> writes:

> I would recommend avoiding shell=True whenever possible. It's used in
> the examples, I suspect, to ease the transition from the functions
> being replaced, but all it takes is for a filename or some other input
> to unexpectedly contain whitespace or a metacharacter and your script
> will stop working--or worse, do damage (cf. the iTunes 2 installer
> debacle[1]).

Agreed, and that's my motivation for learning about ‘subprocess.Popen’.

> Leaving shell=False makes scripts more secure and robust; besides,
> when I'm putting together a command and its arguments, it's as
> convenient to build a list (['mycmd', 'myarg']) as it is a string (if
> not more so).

Which leads to another issue:

I'm modifying a program that gets its child process command arguments
from three places:

* hard-coded text within the program (e.g. the command name, and
  context-specific arguments for the specific operation to be performed)

* user-customised options to be added to the command line

* filenames from the program's own command line

For the hard-coded argument text, obviously they can simply be
hard-coded as list elements::

    command_args = ["foo", "--bar"]

The filenames to be processed can also be appended one item per
filename.

However, the user-customised options are specified by the user in a
configuration file, as a single string argument::

    [fooprogram]
    additional_args = --baz 'crunch cronch' --wobble

This works fine if the command line is constructed by dumb string
concatenation; but obviously it fails when I try to construct a list of
command line arguments.

It's quite reasonable for the user to expect to be able to put any
partial shell command-line in that string option and expect it will be
processed by the shell, including any quoting or other escaping.

How can I take a string that is intended to be part of a command line,
representing multiple arguments and the shell's own escape characters as
in the above example, and end up with a sane command argument list for
‘subprocess.Popen’?

E.g.::

    parser = optparse.OptionParser()
    (options_args) = parser.parse_args(argv[1:])
    filenames = args

    config = configparser.ConfigParser()
    config.read([system_config_file_path, user_config_file_path])
    user_configured_args = config.get('fooprogram', 'additional_args')

    command_args = ["foo", "--bar"]
    somehow_append_each_argument(command_args, user_configured_args)
    command_args.extend(filenames)

    command_process = subprocess.Popen(command_args, shell=False)

The resulting ‘command_args’ list should be::

    ["foo", "--bar",
     "--baz", "crunch cronch", "--wobble",
     "spam.txt", "beans.txt"]

How can I write the ‘somehow_append_each_argument’ step to get that
result?

-- 
 \                “Every sentence I utter must be understood not as an |
  `\                      affirmation, but as a question.” —Niels Bohr |
_o__)                                                                  |
Ben Finney



More information about the Python-list mailing list