Bob5421
Bob5421

Reputation: 9073

python execute command with multiple arguments

I want to run the following command using Python on a Mac OS X computer:

openssl enc -aes-128-cbc -K $(echo -n 'blabla1' | xxd -p) -iv blabla2 -in /tmp/clair -nopad -out /tmp/crypte1 -d

Here is what I have tried:

clef = 'blabla1'
iv = 'blabla2'
arguments = ['openssl','enc', '-aes-128-cbc', '-K $(echo -n \'%s\' | xxd -p)' % clef ,'-iv' ,'%s' % iv,'-in', '/tmp/clair','-nopad','-out','/tmp/crypte1','-d']
execute = Popen(arguments, stdout=PIPE)
out, err = execute.communicate()

The command works fine from a terminal but I get an error from the Python script:

unknown option '-K $(echo -n 'blabla1' | xxd -p)'

I have tried several variants of python shell functions (os.system for example), but I have a problem in each case.

Upvotes: 2

Views: 1968

Answers (2)

Sumit Jha
Sumit Jha

Reputation: 1691

subprocess adds quotes " to the argument "-K $(echo -n 'blabla1' | xxd -p)". You can check by

print(subprocess.list2cmdline(execute.args))

Output:

openssl enc -aes-128-cbc "-K $(echo -n 'blabla1' | xxd -p)" -iv blabla2 -in /tmp/clair -nopad -out /tmp/crypte1 -d

Upvotes: 0

Mad Physicist
Mad Physicist

Reputation: 114270

There are a number of things that users often take for granted when using a shell. Some examples are:

  • Variable expansion (${var})
  • Input/output redirection (cmd > out < in)
  • Pipes (cmd1 | cmd2)
  • Subshell creation ($(cmd))

All of these are features that the shell sets up before passing the actual command to the system. Python's subprocess module does not do any of these things for you automatically, but it does give you the tools to emulate them.

You have correctly redirected your output to a pipe for your Python process to pick up. However, the subshell created by $(echo -n 'blabla1' | xxd -p) is not something that will get processed the way you want without a shell. There are two simple workarounds.

  1. The quick and dirty solution is to pass the entire command line in as a string to subprocess.Popen and set shell=True:

    execute = Popen("openssl enc -aes-128-cbc -K $(echo -n 'blabla1' | xxd -p) "
                    "-iv blabla2 -in /tmp/clair -nopad -out /tmp/crypte1 -d",
                    shell=True)
    

    This will pass the string to a shell instead of directly to the system, thereby giving you access all the behaviors that you expect from a shell. This approach has major security problems, and is not generally recommended. However, it is very easy, and it will get you started.

  2. Implement the convenience of $(... | ...) in your code directly by capturing the output of xxd to a string using subprocess.run. This is lengthier, but probably more robust and portable in the long run. It is certainly the option I would chose in a production environment:

    from subprocess import Popen, run
    
    value = run(['xxd', '-p'], input='blabla1', universal_newlines=True).stdout
    execute = Popen(['openssl', 'enc', '-aes-128-cbc', '-K', value,
                     '-iv', 'blabla2', '-in', '/tmp/clair', '-nopad',
                     '-out', '/tmp/crypte1', '-d'])
    

Since you are setting up your own pipes, you don't need to call echo any more (you never did really, xxd -p <<< 'blabla1' would have worked fine), and -K needs to be a separate argument.

Upvotes: 4

Related Questions