3

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.

1
  • 2
    You can copy the file in python itself. shutil.copy2('/home/pi/project/myPic.jpg', '/home/pi/project/newImage.jpg'). No need to complicate with a subprocess call. Commented Dec 15, 2015 at 0:44

4 Answers 4

3

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.

Sign up to request clarification or add additional context in comments.

Comments

3

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)

1 Comment

Python docs say that using the subprocess module is preferable to using this function.
1

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).

Comments

0

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

9 Comments

Since you have to be responsible for escaping, can you add that to the example?
@thatotherguy I actually did quote the %s in the argument, but I'll call it out more, oops, that still isn't good enough (see the problem with this approach) let me fix it to actually protect the argument more properly!
@thatotherguy ok, should be better now, thanks for the prompt!
Still doesn't work for me for e.g. a filename with spaces.
@thatotherguy if you are using the op script it would also need to be modified to handle a name with spaces
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.