Sanitizing shell arguments in Python

Mikuro

Crotchety UI Nitpicker
For the past day I've been wringing my hands trying to figure out the best way to pass a string variable in Python as an argument to a shell command.

I can't just do this:
Code:
input, output, error = os.popen3("echo " + my_var)
Because if my_var contains, let's say, "'Lala-la'; rm -rf /", then it would delete all my files. It's obviously not safe, so I need to sanitize the input. Fair enough.

AppleScript has a "quoted form of ..." method that's useful for this, but I can't find any equivalent in Python (or Perl, which I also considered for this project).

So, I have two questions:

1. Is there a standard way of doing this that I'm not aware of? Perhaps a shell command that operates on stdin?
2. If I need to write my own function, would it be as simple as replacing all instances of "\" with "\\", replacing all instances of "'" with "\'", and then wrapping the result in single-quotes? i.e.,
Code:
def sanitize_arg(arg):
	return "'" + arg.replace('\\','\\\\').replace('\'','\\\'') + "'"
I don't see why that would be a problem, but....aren't those famous last words? :)

In my Google searching on the topic I've found lots of people like me reinventing the wheel, but I haven't found any authoritative answer. A lot of the sample code I found doesn't even escape backslashes, or just uses string formatting (which as far as I can tell doesn't filter the input at all). Ack!
 
Last edited:
Never mind, I found the solution. Replace this:
Code:
put, get, err = os.popen3("echo " + my_var)
With this:
Code:
put, get, err = os.popen3(["echo", my_var])
If you supply just one string, Python sends it to the shell for interpretation, but if you supply a LIST of strings, then it will take the first entry as the command name and each subsequent entry as an argument. No need to muck around with manual escaping.

As a test I ran this:
Code:
import os
my_var = "'Lala-la'; mkdir /Test"
put, get, err = os.popen3(["echo", my_var])
put.close();
print get.read()
get.close()
err.close()
Which outputs "'Lala-la'; mkdir /Test". Problem solved.

Oh, and also, it looks like os.popen* is deprecated and I'm supposed to use the subprocess module instead. It appears to work the same way (in fact, that's how I discovered that I could use a list in the first place; they don't mention it on the documentation for os.popen*, but they mention it under subprocess).

I hope this will help future Googlers. :)
 
Hi,
A useful way to create programs is to sanitize all data on input, then you can treat it as OK for any output. Having said that, the advice to use subprocess with multiple arguments is sound.

- Paddy.
 
Back
Top