¿Cómo escapar de las llamadas os.system ()?

123

Cuando se usa os.system () a menudo es necesario escapar de los nombres de archivo y otros argumentos pasados ​​como parámetros a los comandos. ¿Cómo puedo hacer esto? Preferiblemente algo que funcione en múltiples sistemas operativos / shells pero en particular para bash.

Actualmente estoy haciendo lo siguiente, pero estoy seguro de que debe haber una función de biblioteca para esto, o al menos una opción más elegante / robusta / eficiente:

def sh_escape(s):
   return s.replace("(","\\(").replace(")","\\)").replace(" ","\\ ")

os.system("cat %s | grep something | sort > %s" 
          % (sh_escape(in_filename), 
             sh_escape(out_filename)))

Editar: he aceptado la respuesta simple de usar comillas, no sé por qué no pensé en eso; Supongo que porque vine de Windows donde 'y "se comportan un poco diferente.

Con respecto a la seguridad, entiendo la preocupación, pero, en este caso, estoy interesado en una solución rápida y fácil que os.system () proporcione, y la fuente de las cadenas no es generada por el usuario o al menos ingresada por un usuario de confianza (yo).

Tom
fuente
1
¡Cuidado con el problema de seguridad! Por ejemplo, si out_filename es foo.txt; rm -rf / El usuario malintencionado puede agregar más comandos directamente interpretados por el shell.
Steve Gury
66
Esto también es útil sin el sistema os., en situaciones donde el subproceso ni siquiera es una opción; por ejemplo, generar scripts de shell.
Una sh_escapefunción ideal sería escapar de los ;espacios y eliminar el problema de seguridad simplemente creando un archivo llamado algo así foo.txt\;\ rm\ -rf\ /.
Tom
En casi todos los casos, debe usar el subproceso, no el sistema os. Llamar a os.system es solo pedir un ataque de inyección.
allyourcode

Respuestas:

84

Esto es lo que uso:

def shellquote(s):
    return "'" + s.replace("'", "'\\''") + "'"

El shell siempre aceptará un nombre de archivo entre comillas y eliminará las comillas circundantes antes de pasarlo al programa en cuestión. En particular, esto evita problemas con los nombres de archivo que contienen espacios o cualquier otro tipo de metacarácter de shell desagradable.

Actualización : si está utilizando Python 3.3 o posterior, use shlex.quote en lugar de rodar el suyo.

Greg Hewgill
fuente
77
@pixelbeat: que es exactamente la razón por la que cierra sus comillas simples, agrega una comilla simple literal escapada y luego vuelve a abrir sus comillas simples.
lhunath
44
Si bien esto no es responsabilidad de la función shellquote, puede ser interesante observar que esto todavía fallará si aparece una barra invertida sin comillas justo antes del valor de retorno de esta función. Moral: asegúrese de usar esto en un código en el que pueda confiar como seguro (como parte de los comandos codificados), no lo agregue a otra entrada de usuario sin comillas.
lhunath
10
Tenga en cuenta que a menos que necesite absolutamente funciones de shell, probablemente debería usar la sugerencia de Jamie.
lhunath
66
Algo similar a esto ahora está oficialmente disponible como shlex.quote .
Janus Troelsen
3
La función proporcionada en esta respuesta hace un mejor trabajo de citación de shell que shlexo pipes. Esos módulos de Python erróneamente asumen que los caracteres especiales son la única cosa que necesitan ser citado, lo que significa que Shell palabras clave (como time, caseo while) se analiza cuando no se espera que el comportamiento. Por esa razón, recomendaría usar la rutina de comillas simples en esta respuesta, porque no trata de ser "inteligente", por lo que no tiene esos casos tontos.
user3035772
157

shlex.quote() hace lo que quieres desde python 3.

(Úselo pipes.quotepara admitir python 2 y python 3)

pixelbeat
fuente
También hay commands.mkarg. También agrega un espacio inicial (fuera de las comillas) que puede o no ser deseable. Es interesante cómo sus implementaciones son bastante diferentes entre sí, y también mucho más complicado que la respuesta de Greg Hewgill.
Laurence Gonsalves
3
Por alguna razón, pipes.quoteno se menciona en la documentación de la biblioteca estándar para el módulo de tuberías
Día
1
Ambos son indocumentados; command.mkarges obsoleto y eliminado en 3.x, mientras que tuberías.quote permaneció.
Beni Cherniavsky-Paskin
9
Corrección: oficialmente documentado como shlex.quote()en 3.3, pipes.quote()retenido por compatibilidad. [ bugs.python.org/issue9723]
Beni Cherniavsky-Paskin
77
las tuberías NO funcionan en Windows: agrega comillas simples en lugar de comillas dobles.
Nux
58

Quizás tenga una razón específica para usar os.system(). Pero si no, probablemente deberías estar usando el subprocessmódulo . Puede especificar las tuberías directamente y evitar usar el shell.

Lo siguiente es de PEP324 :

Replacing shell pipe line
-------------------------

output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]
Jamie
fuente
66
subprocess(especialmente con check_calletc.) a menudo es dramáticamente superior, pero hay algunos casos en los que el escape de shell sigue siendo útil. El principal con el que me encuentro es cuando tengo que invocar comandos remotos ssh.
Craig Ringer
@CraigRinger, sí, el control remoto ssh es lo que me trajo aquí. : PI desearía que ssh tuviera algo para ayudar aquí.
Jürgen A. Erhard
@ JürgenA.Erhard Parece extraño que no tenga una opción --execvp-remote (o que funcione de manera predeterminada). Hacer todo a través del caparazón parece torpe y arriesgado. OTOH, ssh está lleno de peculiaridades extrañas, a menudo las cosas se hacen en una visión estrecha de la "seguridad" que hace que las personas presenten soluciones alternativas mucho más inseguras.
Craig Ringer
10

Tal vez subprocess.list2cmdlinees una mejor oportunidad?

Gary Shi
fuente
Eso se ve bastante bien. Interesante, no está documentado ... (en docs.python.org/library/subprocess.html al menos)
Tom
44
No se escapa correctamente \: subprocess.list2cmdline(["'",'',"\\",'"'])da' "" \ \"
Tino
No escapa a los símbolos de expansión de shell
grep
¿Subprocess.list2cmdline () está destinado solo a Windows?
JS.
@JS Sí, se list2cmdlineajusta a la sintaxis cmd.exe de Windows ( consulte la función docstring en el código fuente de Python ). shlex.quotese ajusta a la sintaxis de shell bourne de Unix, sin embargo, generalmente no es necesario ya que Unix tiene un buen soporte para pasar argumentos directamente. Windows prácticamente requiere que pase una sola cadena con todos sus argumentos (por lo tanto, la necesidad de un escape adecuado).
eestrada
7

Tenga en cuenta que pipes.quote en realidad está roto en Python 2.5 y Python 3.1 y no es seguro de usar: no maneja argumentos de longitud cero.

>>> from pipes import quote
>>> args = ['arg1', '', 'arg3']
>>> print 'mycommand %s' % (' '.join(quote(arg) for arg in args))
mycommand arg1  arg3

Ver Python número 7476 ; Se ha corregido en Python 2.6 y 3.2 y posteriores.

John Wiseman
fuente
44
¿Qué versión de Python estás usando? La versión 2.6 parece producir el resultado correcto: mycommand arg1 '' arg3 (¡Son dos comillas simples juntas, aunque la fuente en Stack Overflow hace que sea difícil de decir!)
Brandon Rhodes
4

Aviso : esta es una respuesta para Python 2.7.x.

Según la fuente , pipes.quote()es una forma de " Citar una cadena de forma confiable como un argumento único para / bin / sh ". (Aunque está en desuso desde la versión 2.7 y finalmente se expone públicamente en Python 3.3 como la shlex.quote()función).

Por otro lado , subprocess.list2cmdline()es una forma de " Traducir una secuencia de argumentos a una cadena de línea de comando, utilizando las mismas reglas que el tiempo de ejecución de MS C ".

Aquí estamos, la forma independiente de la plataforma de citar cadenas para líneas de comando.

import sys
mswindows = (sys.platform == "win32")

if mswindows:
    from subprocess import list2cmdline
    quote_args = list2cmdline
else:
    # POSIX
    from pipes import quote

    def quote_args(seq):
        return ' '.join(quote(arg) for arg in seq)

Uso:

# Quote a single argument
print quote_args(['my argument'])

# Quote multiple arguments
my_args = ['This', 'is', 'my arguments']
print quote_args(my_args)
Rockallita
fuente
3

Creo que os.system solo invoca cualquier shell de comandos configurado para el usuario, por lo que no creo que pueda hacerlo de forma independiente de la plataforma. Mi shell de comandos podría ser cualquier cosa, desde bash, emacs, ruby ​​o incluso quake3. Algunos de estos programas no esperan el tipo de argumentos que les está pasando e incluso si lo hicieran, no hay garantía de que escapen de la misma manera.

pauldoo
fuente
2
No es irrazonable esperar un shell compatible con POSIX en su mayoría o totalmente (al menos en todas partes, pero con Windows, y de todos modos sabes qué "shell" tienes). os.system no usa $ SHELL, al menos no aquí.
2

La función que uso es:

def quote_argument(argument):
    return '"%s"' % (
        argument
        .replace('\\', '\\\\')
        .replace('"', '\\"')
        .replace('$', '\\$')
        .replace('`', '\\`')
    )

es decir: siempre incluyo el argumento entre comillas dobles, y luego la barra invertida entre comillas los únicos caracteres especiales dentro de las comillas dobles.

tzot
fuente
Tenga en cuenta que debe usar '\\ "', '\\ $' y '\`', de lo contrario el escape no ocurre.
JanKanis
1
Además, hay problemas con el uso de comillas dobles en algunas configuraciones regionales (extrañas) ; la solución sugerida usa pipes.quoteque @JohnWiseman señaló también está rota. La respuesta de Greg Hewgill es, por lo tanto, la que hay que usar. (También es el que usan los proyectiles internamente para los casos normales.)
mirabilos
-3

Si utiliza el comando del sistema, intentaría incluir en la lista blanca lo que se incluye en la llamada os.system (). Por ejemplo ...

clean_user_input re.sub("[^a-zA-Z]", "", user_input)
os.system("ls %s" % (clean_user_input))

El módulo de subproceso es una mejor opción, y recomendaría intentar evitar usar algo como os.system / subprocess siempre que sea posible.

dbr
fuente
-3

La verdadera respuesta es: no utilizar os.system()en primer lugar. Utilice en su subprocess.calllugar y proporcione los argumentos sin escape.

Escarabajo
fuente
66
La pregunta contiene un ejemplo donde el subproceso simplemente falla. Si puede usar el subproceso, debe, claro. Pero si no puede ... el subproceso no es una solución para todo . Ah, y su respuesta no responde la pregunta en absoluto.
Jürgen A. Erhard
@ JürgenA.Erhard, ¿no falla el ejemplo del OP porque quiere usar tubos de carcasa? Siempre debe usar un subproceso porque no usa un shell. Este es un ejemplo un poco torpe , pero puede hacer tuberías en subprocesos nativos, hay algunos paquetes de pypi que intentan hacer esto más fácil. Tiendo a hacer el posprocesamiento que necesito en Python tanto como sea posible. Siempre puedes hacer tus propios buffers StringIO y controlar las cosas completamente por completo con subprocesos.
ThorSummoner