Significato effettivo di "shell = True" nel sottoprocesso


260

Chiamo diversi processi con il subprocessmodulo. Tuttavia, ho una domanda.

Nei seguenti codici:

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

e

callProcess = subprocess.Popen(['ls', '-l']) # without shell

Entrambi funzionano. Dopo aver letto i documenti, sono arrivato a saperloshell=True significa eseguire il codice attraverso la shell. Ciò significa che in assenza, il processo viene avviato direttamente.

Quindi cosa dovrei preferire per il mio caso: ho bisogno di eseguire un processo e ottenere il suo output. Che vantaggio ho nel chiamarlo all'interno della shell o al di fuori di esso.


21
il primo comando non è corretto: -lviene passato a /bin/sh(la shell) invece del lsprogramma su Unix seshell=True . shell=TrueNella maggior parte dei casi l' argomento stringa deve essere utilizzato anziché un elenco.
jfs,

1
ri "il processo è avviato direttamente": Wut?
allyourcode

9
La frase "Entrambi funzionano". su quelle 2 chiamate è errato e fuorviante. Le chiamate funzionano in modo diverso. Il solo passaggio da shell=Truea Falsee viceversa è un errore. Dai documenti : "Su POSIX con shell = True, (...) Se args è una sequenza, il primo elemento specifica la stringa di comando e tutti gli elementi aggiuntivi verranno trattati come argomenti aggiuntivi per la shell stessa.". Su Windows c'è la conversione automatica , che potrebbe essere indesiderata.
mbdevpl,

Risposte:


183

Il vantaggio di non chiamare tramite la shell è che non si sta invocando un "programma misterioso". Su POSIX, la variabile di ambiente SHELLcontrolla quale file binario viene richiamato come "shell". Su Windows, non esiste alcun discendente bourne shell, solo cmd.exe.

Quindi invocare la shell invoca un programma a scelta dell'utente ed è dipendente dalla piattaforma. In generale, evitare le invocazioni tramite la shell.

Il richiamo tramite la shell consente di espandere le variabili di ambiente e i file glob secondo il normale meccanismo della shell. Sui sistemi POSIX, la shell espande i globs dei file in un elenco di file. Su Windows, un file glob (ad esempio "*. *") Non viene espanso dalla shell, comunque (ma le variabili di ambiente su una riga di comando vengono espanse da cmd.exe).

Se pensate di volere espansioni di variabili d'ambiente e file globs, cercate gli ILSattacchi del 1992 sui servizi di rete che eseguivano invocazioni di sottoprogrammi tramite la shell. Gli esempi includono le varie sendmailbackdoor che coinvolgono ILS.

In sintesi, utilizzare shell=False.


2
Grazie per la risposta. Anche se non sono davvero in quella fase in cui dovrei preoccuparmi degli exploit, ma capisco a cosa stai arrivando.
user225312

55
Se all'inizio sei disattento, nessuna preoccupazione ti aiuterà a recuperare il ritardo. ;)
Heath Hunnicutt,

Cosa succede se si desidera limitare la memoria massima del sottoprocesso? stackoverflow.com/questions/3172470/…
Pramod

8
l'affermazione su $SHELLnon è corretta. Per citare subprocess.html: "Su Unix con shell=True, la shell ha come impostazione predefinita /bin/sh." (no $SHELL)
marcin,

1
@ user2428107: Sì, se si utilizza la chiamata di backtick su Perl, si sta utilizzando la chiamata di shell e si stanno aprendo gli stessi problemi. Usa arg 3+ opense vuoi modi sicuri per invocare un programma e catturare l'output.
ShadowRanger,

137
>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0

L'impostazione dell'argomento shell su un valore vero fa sì che il sottoprocesso generi un processo shell intermedio e gli dica di eseguire il comando. In altre parole, l'uso di una shell intermedia significa che le variabili, i modelli glob e altre funzioni speciali della shell nella stringa di comando vengono elaborati prima dell'esecuzione del comando. Qui, nell'esempio, $ HOME è stato elaborato prima del comando echo. In realtà, questo è il caso del comando con espansione della shell mentre il comando ls -l è considerato un semplice comando.

fonte: modulo sottoprocesso


16
Non so perché questa non è la risposta selezionata. Di gran lunga quello che corrisponde effettivamente alla domanda
Rodrigo Lopez Guerra,

1
essere d'accordo. questo è un buon esempio per me per capire cosa significa shell = True.
user389955,

2
L'impostazione dell'argomento shell su un valore vero fa sì che il sottoprocesso generi un processo shell intermedio e gli dica di eseguire il comando Oh dio, questo dice tutto. Perché questa risposta non è accettata ??? perché?
pouya,

Penso che il problema sia il primo argomento da chiamare è un elenco, non una stringa, ma che dà l'errore se la shell è False. La modifica del comando in un elenco renderà questo lavoro
Lincoln Randall McFarland

Mi dispiace che il mio commento precedente sia andato prima che fossi finito. Per essere chiari: vedo spesso l'uso di sottoprocessi con shell = True e il comando è una stringa, ad esempio 'ls -l', (mi aspetto di evitare questo errore) ma il sottoprocesso accetta un elenco (e una stringa come elenco di un elemento) . Per eseguire senza invocare una shell (e i relativi problemi di sicurezza ) utilizzare un elenco subprocess.call (['ls', '-l'])
Lincoln Randall McFarland,

42

Un esempio in cui le cose potrebbero andare storte con Shell = True è mostrato qui

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

Controlla il documento qui: subprocess.call ()


6
Il link è molto utile. Come affermato dal collegamento: L'esecuzione di comandi shell che incorporano input non autorizzati da una fonte non attendibile rende un programma vulnerabile all'iniezione shell, un grave difetto di sicurezza che può comportare l'esecuzione arbitraria di comandi. Per questo motivo, l'uso di shell = True è fortemente sconsigliato nei casi in cui la stringa di comando è costruita da input esterni.
jtuki,

39

L'esecuzione di programmi tramite la shell significa che tutto l'input dell'utente passato al programma viene interpretato in base alla sintassi e alle regole semantiche della shell invocata. Nella migliore delle ipotesi, ciò causa solo inconvenienti all'utente, poiché l'utente deve rispettare queste regole. Ad esempio, i percorsi contenenti caratteri shell speciali come virgolette o spazi vuoti devono essere sottoposti a escape. Nel peggiore dei casi, provoca perdite di sicurezza, poiché l'utente può eseguire programmi arbitrari.

shell=Truea volte è utile utilizzare specifiche funzionalità della shell come la suddivisione delle parole o l'espansione dei parametri. Tuttavia, se è richiesta tale funzionalità, utilizzare altri moduli (ad es. os.path.expandvars()Per l'espansione dei parametri oshlex per la suddivisione delle parole). Ciò significa più lavoro, ma evita altri problemi.

In breve: evitare shell=Truecon ogni mezzo.


16

Le altre risposte qui spiegano adeguatamente le avvertenze di sicurezza che sono anche menzionate nella subprocessdocumentazione. Ma oltre a ciò, l'overhead di avviare una shell per avviare il programma che si desidera eseguire è spesso inutile e decisamente sciocco per le situazioni in cui non si utilizza effettivamente nessuna delle funzionalità della shell. Inoltre, l'ulteriore complessità nascosta dovrebbe spaventarti, soprattutto se non hai molta familiarità con la shell o i servizi che fornisce.

Laddove le interazioni con la shell non sono banali, ora è necessario che il lettore e il manutentore dello script Python (che può essere o meno il tuo sé futuro) per comprendere sia lo script Python sia lo script shell. Ricorda il motto di Python "esplicito è meglio che implicito";anche quando il codice Python sarà in qualche modo più complesso dello script di shell equivalente (e spesso molto conciso), potresti essere meglio rimuovere la shell e sostituire la funzionalità con costrutti Python nativi. Ridurre al minimo il lavoro svolto in un processo esterno e mantenere il controllo all'interno del proprio codice per quanto possibile è spesso una buona idea semplicemente perché migliora la visibilità e riduce i rischi di effetti collaterali - desiderati o indesiderati.

Espansione jolly, interpolazione variabile e reindirizzamento sono tutti semplici da sostituire con costrutti Python nativi. Una complessa pipeline di shell in cui parti o tutte non possono essere ragionevolmente riscritte in Python sarebbe l'unica situazione in cui forse potresti prendere in considerazione l'uso della shell. Dovresti comunque assicurarti di comprendere le prestazioni e le implicazioni sulla sicurezza.

Nel caso banale, per evitare shell=True, è sufficiente sostituire

subprocess.Popen("command -with -options 'like this' and\\ an\\ argument", shell=True)

con

subprocess.Popen(['command', '-with','-options', 'like this', 'and an argument'])

Si noti come il primo argomento sia un elenco di stringhe a cui passare execvp()e come non sia necessario (o utile o corretto) quotare stringhe e metacaratteri di shell con escape di backslash. Forse vedi anche Quando racchiudere le virgolette attorno a una variabile shell?

Per inciso, molto spesso vuoi evitare Popense uno dei wrapper più semplici nel subprocesspacchetto fa quello che vuoi. Se hai un Python abbastanza recente, probabilmente dovresti usarlo subprocess.run.

  • Con check=Trueesso fallirà se il comando che hai eseguito non è riuscito.
  • Con stdout=subprocess.PIPEesso acquisirà l'output del comando.
  • Un po 'oscuramente, con universal_newlines=Trueesso decodificherà l'output in una stringa Unicode corretta (è solo bytesnella codifica del sistema altrimenti, su Python 3).

In caso contrario, per molte attività, si desidera check_outputottenere l'output da un comando, verificando che abbia avuto esito positivo o check_callse non è presente alcun output da raccogliere.

Chiuderò con una citazione di David Korn: "È più facile scrivere una shell portatile che uno script di shell portatile". Anche subprocess.run('echo "$HOME"', shell=True)non è portabile su Windows.


Pensavo che la citazione fosse di Larry Wall ma Google mi dice diversamente.
Tripleee

Sono chiacchiere, ma nessun suggerimento tecnico per la sostituzione: eccomi qui, su OS-X, a cercare di acquisire il pid di un'app Mac che ho lanciato tramite 'open': process = subprocess.Popen ('/ usr / bin / pgrep - n '+ app_name, shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE) app_pid, err = process.communicate () --- ma non funziona se non userò shell = True. E adesso?
Motti Shneor,

Ci sono un sacco di domande su come evitare shell=True, molte con risposte eccellenti. Ti è capitato di scegliere quello che è invece il motivo .
Tripleee,

@MottiShneor Grazie per il feedback; aggiunto semplice esempio
tripleee

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.