Significado real de 'shell = True' no subprocesso

260

Estou chamando processos diferentes com o subprocessmódulo. No entanto, eu tenho uma pergunta.

Nos seguintes códigos:

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

e

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

Ambos funcionam. Depois de ler os documentos, soube que isso shell=Truesignifica executar o código através do shell. Então, na ausência, o processo é iniciado diretamente.

Então, o que devo preferir para o meu caso - preciso executar um processo e obter sua saída. Que benefício eu tenho ao chamá-lo de dentro ou fora do shell?

user225312
fonte
21
o primeiro comando está incorreto: -lé passado para /bin/sh(o shell) em vez de lsprograma no Unix seshell=True . O argumento string deve ser usado shell=Truena maioria dos casos, em vez de uma lista.
jfs
1
re "o processo é iniciado diretamente": Wut?
Allyourcode
9
A declaração "Ambos funcionam". essas duas chamadas são incorretas e enganosas. As chamadas funcionam de maneira diferente. Basta mudar de shell=Truea Falsee vice-versa é um erro. From docs : "No POSIX com shell = True, (...) Se args for uma sequência, o primeiro item especificará a sequência de comandos e quaisquer itens adicionais serão tratados como argumentos adicionais para o próprio shell.". No Windows, há conversão automática , o que pode ser indesejável.
mbdevpl
Veja também stackoverflow.com/q/59641747/874188
tripleee

Respostas:

183

O benefício de não ligar através do shell é que você não está invocando um 'programa misterioso'. No POSIX, a variável de ambiente SHELLcontrola qual binário é chamado como o "shell". No Windows, não há descendente do shell bourne, apenas o cmd.exe.

Portanto, invocar o shell chama um programa de escolha do usuário e depende da plataforma. De um modo geral, evite invocações através do shell.

A chamada por meio do shell permite expandir variáveis ​​de ambiente e arquivar globs de acordo com o mecanismo usual do shell. Nos sistemas POSIX, o shell expande os globs de arquivo para uma lista de arquivos. No Windows, um arquivo glob (por exemplo, "*. *") Não é expandido pelo shell, de qualquer maneira (mas as variáveis ​​de ambiente em uma linha de comando são expandidas pelo cmd.exe).

Se você acha que deseja expansões de variáveis ​​de ambiente e globs de arquivos, pesquise os ILSataques do 1992-ish em serviços de rede que executavam invocações de subprogramas por meio do shell. Exemplos incluem as várias sendmailbackdoors envolvendo ILS.

Em resumo, use shell=False.

Heath Hunnicutt
fonte
2
Obrigado pela resposta. Embora eu realmente não esteja nessa fase em que devo me preocupar com explorações, mas entendo o que você está fazendo.
precisa saber é o seguinte
55
Se você é descuidado no começo, nenhuma preocupação o ajudará a se atualizar mais tarde. ;)
Heath Hunnicutt
E se você quiser limitar a memória máxima do subprocesso? stackoverflow.com/questions/3172470/…
Pramod,
8
a afirmação sobre $SHELLnão está correta. Para citar subprocess.html: "No Unix com shell=True, o shell é padronizado como /bin/sh". (não $SHELL)
Marcin
1
@ user2428107: Sim, se você usa a chamada de backtick no Perl, está usando a chamada de shell e abrindo os mesmos problemas. Use 3+ arg opense desejar maneiras seguras de chamar um programa e capturar a saída.
ShadowRanger 29/10
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

Definir o argumento do shell como um valor verdadeiro faz com que o subprocesso gere um processo intermediário do shell e peça para executar o comando. Em outras palavras, o uso de um shell intermediário significa que variáveis, padrões glob e outros recursos especiais do shell na cadeia de comandos são processados ​​antes da execução do comando. Aqui, no exemplo, $ HOME foi processado antes do comando echo. Na verdade, este é o caso do comando com expansão de shell, enquanto o comando ls -l é considerado um comando simples.

fonte: Módulo Subprocesso

Mina Gabriel
fonte
16
Não sei por que essa não é a resposta selecionada. De longe aquele que realmente corresponde à questão #
Rodrigo Lopez Guerra
1
aceita. este é um bom exemplo para eu entender o que significa shell = True.
user389955
2
Definir o argumento do shell como um valor verdadeiro faz com que o subprocesso inicie um processo intermediário do shell e diga para executar o comando Oh, Deus, isso diz tudo. Por que essa resposta não é aceita ??? porque?
pouya 24/09/17
Eu acho que o problema é o primeiro argumento a chamar é uma lista, não uma string, mas isso gera o erro se o shell for False. Mudar o comando para uma lista fará com que isso funcione
Lincoln Randall McFarland
Desculpe meu comentário anterior foi antes de eu terminar. Para ser claro: muitas vezes vejo o subprocesso ser usado com shell = True e o comando é uma sequência, por exemplo, 'ls -l' (espero evitar esse erro), mas o subprocesso recebe uma lista (e uma sequência como uma lista de um elemento) . Para executar com a invocação de um shell (e as questões de segurança com que ) usar um subprocess.call lista ([ 'ls', '-l'])
Lincoln Randall McFarland
42

Um exemplo em que as coisas podem dar errado com Shell = True é mostrado aqui

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

Verifique o documento aqui: subprocess.call ()

Richeek
fonte
6
O link é muito útil. Como o link afirmava: A execução de comandos do shell que incorporam entradas não autorizadas de uma fonte não confiável torna um programa vulnerável à injeção de shell, uma falha grave de segurança que pode resultar na execução arbitrária de comandos. Por esse motivo, o uso de shell = True é altamente desencorajado nos casos em que a cadeia de comandos é construída a partir de entrada externa.
jtuki
39

A execução de programas através do shell significa que toda a entrada do usuário passada para o programa é interpretada de acordo com as regras de sintaxe e semântica do shell chamado. Na melhor das hipóteses, isso só causa transtornos ao usuário, porque ele deve obedecer a essas regras. Por exemplo, caminhos que contenham caracteres especiais de shell, como aspas ou espaços em branco, devem ser escapados. Na pior das hipóteses, causa vazamentos de segurança, porque o usuário pode executar programas arbitrários.

shell=TrueÀs vezes, é conveniente usar recursos específicos do shell, como divisão de palavras ou expansão de parâmetros. No entanto, se esse recurso for necessário, utilize outros módulos (por exemplo, os.path.expandvars()para expansão de parâmetros ou shlexdivisão de palavras). Isso significa mais trabalho, mas evita outros problemas.

Em resumo: Evite shell=Truepor todos os meios.

lunaryorn
fonte
16

As outras respostas aqui explicam adequadamente as advertências de segurança que também são mencionadas na subprocessdocumentação. Além disso, a sobrecarga de iniciar um shell para iniciar o programa que você deseja executar geralmente é desnecessária e definitivamente boba para situações em que você realmente não usa nenhuma das funcionalidades do shell. Além disso, a complexidade oculta adicional deve assustá-lo, especialmente se você não estiver muito familiarizado com o shell ou os serviços que ele fornece.

Onde as interações com o shell não são triviais, você agora exige que o leitor e o mantenedor do script Python (que pode ou não ser seu futuro) entendam tanto o Python quanto o shell. Lembre-se do lema do Python "explícito é melhor que implícito"; mesmo quando o código Python for um pouco mais complexo que o script de shell equivalente (e geralmente muito conciso), é melhor remover o shell e substituir a funcionalidade pelas construções nativas do Python. Minimizar o trabalho realizado em um processo externo e manter o controle dentro do seu próprio código, na medida do possível, geralmente é uma boa idéia, simplesmente porque melhora a visibilidade e reduz os riscos de efeitos colaterais desejados ou indesejados.

Expansão curinga, interpolação variável e redirecionamento são todos fáceis de substituir por construções nativas do Python. Um complexo pipeline de shell em que partes ou todas não possam ser razoavelmente reescritas no Python seria a única situação em que talvez você possa considerar o uso do shell. Você ainda deve entender as implicações de desempenho e segurança.

No caso trivial, para evitar shell=True, basta substituir

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

com

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

Observe como o primeiro argumento é uma lista de cadeias de caracteres a serem passadas execvp()e como citar cadeias de caracteres e metacaracteres de shell com barra invertida geralmente não são necessários (ou úteis ou corretos). Talvez veja também Quando agrupar aspas em torno de uma variável de shell?

Como um aparte, muitas vezes você deseja evitar Popense um dos invólucros mais simples do subprocesspacote fizer o que você deseja. Se você tem um Python recente o suficiente, provavelmente deve usar subprocess.run.

  • Com check=Trueele falhará se o comando que você executou falhar.
  • Com stdout=subprocess.PIPEele irá capturar a saída do comando.
  • De maneira um tanto obscura, universal_newlines=Trueele decodificará a saída em uma string Unicode adequada (apenas bytesna codificação do sistema, no Python 3).

Caso contrário, para muitas tarefas, você deseja check_outputobter a saída de um comando, enquanto verifica se foi bem-sucedido ou check_callse não há saída a ser coletada.

Termino com uma citação de David Korn: "É mais fácil escrever um shell portátil do que um script de shell portátil". Even subprocess.run('echo "$HOME"', shell=True)não é portátil para Windows.

triplo
fonte
Eu pensei que a citação era de Larry Wall, mas o Google me diz o contrário.
tripleee
Isso é conversa fiada - mas nenhuma sugestão técnica para substituição: Aqui estou, no OS-X, tentando adquirir o pid de um Mac App que lancei via 'open': process = subprocess.Popen ('/ usr / bin / pgrep - n '+ app_name, shell = False, stdout = subprocesso.PIPE, stderr = subprocesso.PIPE) app_pid, err = process.communicate () --- mas não funciona a menos que eu use shell = True. O que agora?
Motti Shneor 03/07/19
Há muitas perguntas sobre como evitar shell=True, muitas com excelentes respostas. Por acaso, você escolheu o que é o motivo .
Tripleee 03/07
@MottiShneor Obrigado pelo feedback; exemplo simples adicionado
tripleee 03/07