idiaz01
idiaz01

Reputation: 43

python bash command: how to escape single quote?

I'm trying to execute a shell command through python. The command is like the following one:

su -c "lftp -c 'open -u user,password ftp://127.0.0.1; get ivan\'s\ filename.pdf' " someuser

So, when I try to do it in python:

command = "su -c \"lftp -c 'open -u user,password ftp://127.0.0.1; get ivan\'s\ filename.pdf' \" someuser"
os.system(command)

Or:

command = subprocess.Popen(["su", "-c", "lftp -c 'open -u user,password ftp://127.0.0.1; get ivan\'s\ filename.pdf'", "someuser"])

I get the following error:

bash: -c: line 0: unexpected EOF while looking for matching `''
bash: -c: line 1: syntax error: unexpected end of file

Referred to: ivan\'s single quote.

I know there are a lot of single/double quotes in that but how can I escape this?

Thanks in advance!

EDIT: THIS WORKED FOR ME:

subprocess.call(["su","-c",r"""lftp -c "open -u user,password ftp://127.0.0.1; get ivan\'s\ filename.pdf" """, "someuser"])

Thank you all very much!

Upvotes: 3

Views: 6789

Answers (2)

Gert van den Berg
Gert van den Berg

Reputation: 2785

If you printed your test string you would notice that it results in the following:

su -c "lftp -c 'open -u user,password ftp://127.0.0.1; get ivan's\ filename.pdf' " someuser

The problem is that you need to escape the slash that you use to escape the single quote in order to keep Python from eating it.

command = "su -c \"lftp -c 'open -u user,password ftp://127.0.0.1; get ivan\\'s\\ filename.pdf' \" someuser"

will get the backslash across, you will then get an error from lftp instead...

This works:

command = "su -c \"lftp -c \\\"open -u user,password ftp://127.0.0.1; get ivan\\'s\\ filename.pdf\\\" \" someuser"

(It uses (escaped) double quotes instead, to ensure that the shell started by su still interprets the escape sequences)

(os.system(a) effectively does subprocess.call(["sh","-c",a]), which means that sh sees su -c "lftp -c 'open -u user,password ftp://127.0.0.1; get ivan's\ filename.pdf' " someuser (for the original one). It does escape sequence processing on this and sees an unclosed single quote (it is initially closed by ivan'), resulting in your error). Once that is fixed, sh calls su, which in turn starts up another instance of sh doing more escape processing, resulting in the error from lftp (since sh doesn't handle escape sequences in the single quotes)

subprocess.call() or curl are better ways to implement this - curl will need much less escaping, you can use curl "ftp://user:[email protected]/ivan's filename.pdf" on the command line, some more escaping is needed for going viasu -cand for python.sudoinstead ofsu` also results in less escaping being needed....

If you want to use subprocess.call() (which removes one layer of shell), you can use

subprocess.call(["su","-c","lftp -c \\\"open -u user,password ftp://127.0.0.1; get ivan\\'s\\ filename.pdf\\\"", "someuser"])

(The problem is that python deals with one level of escaping, and the sh -c invoked from su with the next layer... This results in quite an ugly command...) (different quotes might slightly reduce that...)

Using r"" can get rid of the python level escape processing: (needing only the shell level escapes) (Using triple quotes to allow quotes in the string)

subprocess.call(["su","-c",r"""lftp -c \"open -u user,password ftp://127.0.0.1; get ivan\'s\ filename.pdf\"""", "someuser"])

Adding a space allows for stripping the shell escapes, since lftp doesn't seem to need the filename escaped for the spaces and single quote.

subprocess.call(["su","-c",r"""lftp -c "open -u user,password ftp://127.0.0.1; get ivan's filename.pdf" """, "someuser"])

This results in the eventual lftp ARGV being

["lftp","-c","open -u user,password ftp://127.0.0.1; get ivan's filename.pdf"]

For curl instead (it still ends up bad due to the su being involved):

subprocess.call(["su","-c",r"""curl "ftp://user:[email protected]/ivan's filename.pdf" """, "someuser"])

Upvotes: 3

Pitto
Pitto

Reputation: 8589

Using subprocess.call() is the best and more secure way to perform this task.

Here's an example from the documentation page:

subprocess.call(["ls", "-l"]) # As you can see we have here the command and a parameter

About the error I think it is something related to the spaces and the ' charachter.

Try using string literals (pay attention to the r before the string, also be sure that the command is 100% matching the one you use in BASH):

r"My ' complex & string"

So, in your case:

command = subprocess.Popen(["su", "-c", r"lftp -c 'open -u user,password ftp://127.0.0.1; get ivan's filename.pdf'", "someuser"])

Upvotes: 1

Related Questions