RRWW
RRWW

Reputation: 137

Passing arguments to shell script from Python

I'm writing a python program which passes arguments to a shell script.

Here's my python code:

import subprocess

Process=subprocess.Popen('./copyImage.sh %s' %s str(myPic.jpg))

And my "copyImage.sh":

#!/bin/sh

cp /home/pi/project/$1 /home/pi/project/newImage.jpg

I can run the script on terminal without problems. But when executing the python code, the terminal returned "NameError: name 'myPic' is not defined".

If I change the syntax to

Process=subprocess.Popen('./copyImage.sh %s' %s "myPic.jpg")

Then the terminal returned "OSError: [Errno 2] No such file or directory".

I've followed this: Python: executing shell script with arguments(variable), but argument is not read in shell script but it didn't help.

Upvotes: 3

Views: 8639

Answers (4)

david m lee
david m lee

Reputation: 2647

Using os.system call is the way to go as:

  1. os.system does find your shell script in the environment
  2. you can append as many arguments as you need to the destination shell script

Example:

os.system('myshellscript1 ' + arg1 + ' ' + arg2)

Upvotes: 3

Eric Renouf
Eric Renouf

Reputation: 14490

While you got 2 answers that show how to use subprocess with an iterable for the arguments and I would recommend going one of those ways, for completeness you can use a string containing the full command if you specify shell=True, but then you're responsible for all the quoting and everything like that for arguments.

Process=subprocess.Popen('./copyImage.sh %s' % shlex.quote("myPic.jpg"), shell=True)

Note that in addition to adding shell=True I pass the argument through shlex.quote to let it handle escaping any special characters to make it a bit safer if the filename came from a user input, otherwise it could include a ; and another command to run, for example. Input like myPic.jpg; rm -rf ~ would otherwise cause bad things to happen when executed.

If you don't specify shell=True the subrpocess module will actually be looking for an executable named copyImage.sh myPic.jpg with the space and both words as the name of the executable to run.

Two further notes, for python 2 instead of shlex.quote use pipes.quote. Also, the shell script above does not quote its arguments, so will not work with names with spaces or other special characters. It should be modified to quote its variables (which is always a good idea):

#!/bin/sh

cp /home/pi/project/"$1" /home/pi/project/newImage.jpg

With a slightly different script:

#!/bin/bash
printf 'Arg 1 is: %s\n' "$1"

we can see this work as follows:

subprocess.check_call("./demoScript.sh %s" % shlex.quote("This has ; bad stuff"), shell=True)

which produces the following output on stdout

Arg 1 is: This has ; bad stuff

Upvotes: 0

wim
wim

Reputation: 362507

The subprocess module is expecting a list of arguments, not a space-separated string. The way you tried caused python to look for a program called "copyImage.sh myPic.jpg" and call it with no arguments, whereas you wanted to look for a program called copyImage.sh and call it with one argument.

subprocess.check_call(['copyImage.sh', 'myPic.jpg'])

I also want to mention, since your script simply calls copy in a shell, you should probably cut out the middleman and just use python's shutil.copy directly. It's a more appropriate tool than running a subprocess for this task.

Upvotes: 3

that other guy
that other guy

Reputation: 123410

The safe and robust way is:

subprocess.Popen(["./copyImage.sh", "myPic.jpg"])

Your first attempt failed because string literals need quotes in Python. The second one failed because Popen doesn't run a shell by default (the question you link sets Shell=true to do this, but it's fragile and bad).

Upvotes: 1

Related Questions