Come sfuggire alle chiamate os.system ()?


124

Quando si usa os.system () è spesso necessario sfuggire ai nomi dei file e ad altri argomenti passati come parametri ai comandi. Come posso fare questo? Preferibilmente qualcosa che funzioni su più sistemi operativi / shell, ma in particolare per bash.

Attualmente sto facendo quanto segue, ma sono sicuro che ci deve essere una funzione di libreria per questo, o almeno un'opzione più elegante / robusta / efficiente:

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

Modifica: ho accettato la semplice risposta di usare le virgolette, non so perché non ci ho pensato; Immagino perché vengo da Windows dove "e" si comportano in modo leggermente diverso.

Per quanto riguarda la sicurezza, capisco la preoccupazione, ma, in questo caso, sono interessato a una soluzione semplice e veloce fornita da os.system () e la fonte delle stringhe non è generata dall'utente o almeno inserita da un utente fidato (io).


1
Attenzione al problema di sicurezza! Ad esempio, se out_filename è foo.txt; rm -rf / L'utente malintenzionato può aggiungere più comandi interpretati direttamente dalla shell.
Steve Gury il

6
Questo è utile anche senza os.system, in situazioni in cui il sottoprocesso non è nemmeno un'opzione; es. generazione di script di shell.

Una sh_escapefunzione ideale sarebbe sfuggire agli ;spazi e e rimuovere il problema di sicurezza semplicemente creando un file chiamato qualcosa di simile foo.txt\;\ rm\ -rf\ /.
Tom

In quasi tutti i casi, dovresti usare sottoprocesso, non os.system. Chiamare os.system sta solo chiedendo un attacco injection.
allyourcode

Risposte:


85

Questo è quello che uso:

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

La shell accetterà sempre un nome di file tra virgolette e rimuoverà le virgolette circostanti prima di passarlo al programma in questione. In particolare, questo evita problemi con nomi di file che contengono spazi o qualsiasi altro tipo di metacarattere di shell sgradevole.

Aggiornamento : se stai usando Python 3.3 o successivo, usa shlex.quote invece di lanciarne uno tuo.


7
@ pixelbeat: che è esattamente il motivo per cui chiude le virgolette singole, aggiunge una virgoletta singola letterale con escape e quindi riapre nuovamente le virgolette singole.
lhunath

4
Anche se questa non è certo responsabilità della funzione shellquote, potrebbe essere interessante notare che ciò fallirà comunque se una barra rovesciata non quotata appare appena prima del valore di ritorno di questa funzione. Morale: assicurati di usarlo nel codice di cui ti puoi fidare come sicuro - (come parte di comandi codificati) - non aggiungerlo ad altri input utente non quotati.
lhunath

10
Nota che a meno che tu non abbia assolutamente bisogno delle funzionalità della shell, dovresti probabilmente usare il suggerimento di Jamie.
lhunath

6
Qualcosa di simile a questo è ora ufficialmente disponibile come shlex.quote .
Janus Troelsen

3
La funzione fornita in questa risposta fa un lavoro migliore di quotazione di shell rispetto a shlexo pipes. Quei moduli python presumono erroneamente che i caratteri speciali siano l'unica cosa che deve essere citata, il che significa che le parole chiave della shell (come time, caseo while) verranno analizzate quando quel comportamento non è previsto. Per questo motivo consiglierei di usare la routine di virgolette singole in questa risposta, perché non cerca di essere "intelligente", quindi non ha quei casi limite.
user3035772

157

shlex.quote() fa quello che vuoi da python 3.

(Utilizzare pipes.quoteper supportare sia python 2 che python 3)


C'è anche commands.mkarg. Aggiunge anche uno spazio iniziale (al di fuori delle virgolette) che può o non può essere desiderabile. È interessante come le loro implementazioni siano abbastanza diverse l'una dall'altra e anche molto più complicate della risposta di Greg Hewgill.
Laurence Gonsalves

3
Per qualche ragione, pipes.quotenon è menzionato dalla documentazione della libreria standard per il modulo pipe
Giorno

1
Entrambi sono privi di documenti; command.mkargè deprecato e rimosso in 3.x, mentre pipe.quote è rimasto.
Beni Cherniavsky-Paskin

9
Correzione: ufficialmente documentato come shlex.quote()in 3.3, pipes.quote()mantenuto per compatibilità. [ bugs.python.org/issue9723]
Beni Cherniavsky-Paskin

7
pipe NON funziona su Windows: aggiunge virgolette singole invece di virgolette doppie.
Nux

58

Forse hai un motivo specifico per utilizzare os.system(). Ma in caso contrario dovresti probabilmente usare il subprocessmodulo . È possibile specificare direttamente le pipe ed evitare di utilizzare la shell.

Quanto segue è da 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]

6
subprocess(specialmente con check_callecc.) è spesso notevolmente superiore, ma ci sono alcuni casi in cui l'escape della shell è ancora utile. Quello principale in cui mi imbatto è quando devo invocare comandi remoti ssh.
Craig Ringer

@ CraigRinger, sì, ssh remoting è ciò che mi ha portato qui. : Vorrei che SSH avesse qualcosa da aiutare qui.
Jürgen A. Erhard

@ JürgenA.Erhard Sembra strano che non abbia un'opzione --execvp-remote (o funzioni in questo modo per impostazione predefinita). Fare tutto attraverso il guscio sembra goffo e rischioso. OTOH, ssh è pieno di stranezze, spesso cose fatte in una visione ristretta della "sicurezza" che fa sì che le persone escano con soluzioni alternative molto più insicure.
Craig Ringer

10

Forse subprocess.list2cmdlineè uno scatto migliore?


Sembra piuttosto buono. Interessante che non sia documentato ... (in docs.python.org/library/subprocess.html almeno)
Tom

4
Non scappa correttamente \: subprocess.list2cmdline(["'",'',"\\",'"'])' "" \ \"
Tino

Non sfugge ai simboli di espansione della shell
grep

Subprocess.list2cmdline () è inteso solo per Windows?
JS.

@JS Sì, è list2cmdlineconforme alla sintassi cmd.exe di Windows ( vedere la funzione docstring nel codice sorgente di Python ). shlex.quoteè conforme alla sintassi della shell bourne di Unix, tuttavia di solito non è necessario poiché Unix ha un buon supporto per il passaggio diretto degli argomenti. Windows richiede praticamente che tu passi una singola stringa con tutti i tuoi argomenti (quindi la necessità di un corretto escape).
eestrada

7

Nota che pipe.quote è effettivamente rotto in Python 2.5 e Python 3.1 e non è sicuro da usare - Non gestisce argomenti di lunghezza zero.

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

Vedi Python problema 7476 ; è stato corretto in Python 2.6 e 3.2 e successivi.


4
Quale versione di Python stai usando? La versione 2.6 sembra produrre l'output corretto: mycommand arg1 '' arg3 (Queste sono due virgolette singole insieme, anche se il carattere su Stack Overflow lo rende difficile da dire!)
Brandon Rhodes

4

Avviso : questa è una risposta per Python 2.7.x.

Secondo la fonte , pipes.quote()è un modo per " Citare in modo affidabile una stringa come singolo argomento per / bin / sh ". (Sebbene sia deprecato dalla versione 2.7 e infine esposto pubblicamente in Python 3.3 comeshlex.quote() funzione.)

D' altra parte , subprocess.list2cmdline()è un modo per " tradurre una sequenza di argomenti in una stringa della riga di comando, utilizzando le stesse regole del runtime di MS C ".

Eccoci qui, il modo indipendente dalla piattaforma di quotare le stringhe per le righe di 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)

3

Credo che os.system invoca semplicemente la shell dei comandi configurata per l'utente, quindi non penso che tu possa farlo in modo indipendente dalla piattaforma. La mia shell dei comandi potrebbe essere qualsiasi cosa da bash, emacs, ruby ​​o persino quake3. Alcuni di questi programmi non si aspettano il tipo di argomenti che stai passando loro e anche se lo facessero, non c'è alcuna garanzia che scappino allo stesso modo.


2
Non è irragionevole aspettarsi una shell per lo più o completamente conforme a POSIX (almeno ovunque ma con Windows, e in ogni caso sai quale "shell" hai). os.system non usa $ SHELL, almeno non qui.

2

La funzione che utilizzo è:

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

ovvero: racchiudo sempre l'argomento tra virgolette doppie, quindi tra virgolette doppie gli unici caratteri speciali tra virgolette.


Nota che dovresti usare '\\ "', '\\ $' e '\`', altrimenti l'escaping non avviene.
JanKanis

1
Inoltre, ci sono problemi con l'utilizzo di virgolette doppie in alcune (strane) lingue ; la correzione suggerita utilizza pipes.quoteche @JohnWiseman ha sottolineato è anch'essa non funzionante. La risposta di Greg Hewgill è quindi quella da usare. (È anche quello che le conchiglie usano internamente per i casi normali.)
mirabilos

-3

Se usi il comando di sistema, proverei a inserire nella whitelist ciò che entra nella chiamata os.system () .. Ad esempio ..

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

Il modulo subprocess è un'opzione migliore e consiglierei di evitare di usare qualcosa come os.system / subprocess ove possibile.


-3

La vera risposta è: non usare os.system()in primo luogo. Usa subprocess.callinvece e fornisci gli argomenti senza caratteri di escape.


6
La domanda contiene un esempio in cui il sottoprocesso fallisce. Se puoi usare un sottoprocesso, dovresti, certo. Ma se non puoi ... il sottoprocesso non è una soluzione per tutto . Oh, e la tua risposta non risponde affatto alla domanda.
Jürgen A. Erhard

@ JürgenA.Erhard l'esempio dell'OP non fallisce perché vuole usare i tubi di shell? Dovresti sempre usare sottoprocesso perché non usa una shell. Questo è un esempio un po 'goffo , ma puoi fare pipe in sottoprocessi nativi, ci sono alcuni pacchetti pypi che cercano di renderlo più semplice. Tendo a fare solo la post-elaborazione di cui ho bisogno in Python il più possibile, puoi sempre creare i tuoi buffer StringIO e controllare le cose abbastanza completamente con i sottoprocessi.
ThorSummoner
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.