Como lançar processos externos com Python e o módulo de subprocesso

Como lançar processos externos com Python e o módulo de subprocesso

Em nossos scripts de automação, geralmente precisamos lançar e monitorar programas externos para realizar nossas tarefas desejadas. Ao trabalhar com o Python, podemos usar o módulo de subprocesso para executar as referidas operações. Este módulo faz parte da biblioteca padrão da linguagem de programação. Neste tutorial, daremos uma rápida olhada nele, e aprenderemos o básico de seu uso.

Neste tutorial, você aprenderá:

  • Como usar a função "executar" para gerar um processo externo
  • Como capturar uma saída padrão de processo e erro padrão
  • Como verificar o status de existência de um processo e levantar uma exceção se falhar
  • Como executar um processo em uma concha intermediária
  • Como definir um tempo limite para um processo
  • Como usar a classe Popen diretamente para transmitir dois processos
Como lançar processos externos com Python e o módulo de subprocesso

Requisitos de software e convenções usadas

Requisitos de software e convenções de linha de comando Linux
Categoria Requisitos, convenções ou versão de software usada
Sistema Distribuição Independente
Programas Python3
Outro Conhecimento de Python e Programação Orientada a Objetos
Convenções # - requer que os comandos linux -comidos sejam executados com privilégios de raiz diretamente como usuário root ou por uso de sudo comando
$-exige que o Linux-Commands seja executado como um usuário não privilegiado regular

A função "run"

O correr função foi adicionada ao subprocesso módulo apenas em versões relativamente recentes do Python (3.5). Usá -lo agora é a maneira recomendada de gerar processos e deve cobrir os casos de uso mais comuns. Antes de tudo, vamos ver seu uso mais simples. Suponha que queremos executar o ls -al comando; Em uma concha de python, correríamos:

>>> Importar subprocesso >>> Process = Subprocess.Run (['ls', '-l', '-a']) 

A saída do comando externo é exibida na tela:

Total 132 DRWX------. 22 EGDOC EGDOC 4096 30 de novembro 12:18 . drwxr-xr-x. 4 raiz raiz 4096 22 de novembro 13: 11… -rw-------. 1 EGDOC EGDOC 10438 1 12:54 .Bash_history -rw-r-r--. 1 Egdoc Egdoc 18 de julho 27 15:10 .Bash_logout […] 

Aqui, apenas usamos o primeiro argumento obrigatório aceito pela função, que pode ser uma sequência que "descreve" um comando e seus argumentos (como no exemplo) ou uma string, que deve ser usada ao executar com o shell = true argumento (veremos mais tarde).

Captura o comando stdout e stderr

E se não queremos que a saída do processo seja exibida na tela, mas capturada, para que possa ser referenciada após a saída do processo? Nesse caso, podemos definir o capture_output argumento da função para Verdadeiro:

>>> Process = subprocesso.Run (['ls', '-l', '-a'], capture_output = true) 

Como podemos recuperar a saída (stdout e stderr) do processo depois? Se você observar os exemplos acima, pode ver que usamos o processo variável para referenciar o que é retornado pelo correr função: a ConclitedProcess objeto. Este objeto representa o processo que foi lançado pela função e possui muitas propriedades úteis. Entre os outros, stdout e stderr são usados ​​para "armazenar" os descritores correspondentes do comando se, como dissemos, o capture_output argumento está definido como Verdadeiro. Nesse caso, para obter o stdout do processo que iríamos executar:

>>> Processo.stdout 

Stdout e stderr são armazenados como Sequências de bytes por padrão. Se queremos que eles sejam armazenados como cordas, devemos definir o texto argumento do correr função para Verdadeiro.



Gerenciar uma falha do processo

O comando que executamos nos exemplos anteriores foi executado sem erros. Ao escrever um programa, no entanto, todos os casos devem ser levados em consideração, e se um processo gerado falhar? Por padrão, nada de "especial" aconteceria. Vamos ver um exemplo; nós executamos o ls comando novamente, tentando listar o conteúdo do /raiz Diretório, que normalmente, no Linux não é legível por usuários normais:

>>> Process = subprocesso.Run (['ls', '-l', '-a', '/root'])) 

Uma coisa que podemos fazer para verificar se um processo lançado falhou é verificar seu status de existência, que é armazenado no Código de retorno propriedade do ConclitedProcess objeto:

>>> Processo.ReturnCode 2 

Ver? Neste caso o Código de retorno era 2, confirmando que o processo encontrou um problema de permissão e não foi concluído com sucesso. Poderíamos testar a saída de um processo dessa maneira, ou mais elegantemente poderíamos fazer para que uma exceção seja levantada quando uma falha acontecer. Introduzir o verificar argumento do correr função: quando está definido como Verdadeiro e um processo gerado falha, o ChamadoProcessError Exceção é levantada:

>>> Process = subprocesso.Run (['ls', '-l', '-a', '/root'], check = true) ls: não é possível abrir o diretório '/root': permissão negado traceback (chamada mais recente): arquivo "" " , linha 1, no arquivo "/usr/lib64/python3.9/Subprocesso.py ", linha 524, em corrida Raise chamadaProcesSError (Retcode, Process.Args, subprocesso.ChamadoProcessError: Command '[' LS ',' -l ',' -a ','. 

Manuseio exceções No Python é bastante fácil, para gerenciar uma falha de processo, poderíamos escrever algo como:

>>> Tente:… Process = Subprocess.Run (['ls', '-l', '-a', '/root'], check = true)… exceto subprocesso.ChamadoProcessError como e:… # apenas um exemplo, algo útil para gerenciar a falha deve ser feita!… Print (f "e.CMD falhou!")… Ls: não é possível abrir o diretório '/root': permissão negada ['ls', '-l', '-a', '/root'] falhou! >>> 

O ChamadoProcessError a exceção, como dissemos, é aumentada quando um processo sai com um não 0 status. O objeto tem propriedades como Código de retorno, cmd, stdout, stderr; O que eles representam é bastante óbvio. No exemplo acima, por exemplo, acabamos de usar o cmd propriedade, para relatar a sequência usada para descrever o comando e seus argumentos na mensagem que escrevemos quando a exceção ocorreu.

Executar um processo em uma concha

Os processos lançados com o correr função, são executados "diretamente", isso significa que nenhum shell é usado para iniciá -los: nenhuma variável de ambiente está disponível para o processo e as expansões de concha não são executadas. Vamos ver um exemplo que envolve o uso do $ Home variável:

>>> Process = subprocesso.Run (['ls', '-al', '$ home']) LS: Não é possível acessar '$ home': nenhum arquivo ou diretório 

Como você pode ver o $ Home A variável não foi expandida. Os processos de execução dessa maneira são recomendados para evitar possíveis riscos de segurança. Se em certos casos, no entanto, precisamos invocar uma concha como um processo intermediário, precisamos definir o concha parâmetro do correr função para Verdadeiro. Nesses casos, é preferível especificar o comando a ser executado e seus argumentos como um corda:

>>> Process = subprocesso.RUN ('LS -Al $ home', shell = true) Total 136 DRWX------. 23 EGDOC EGDOC 4096 3 09:35 . drwxr-xr-x. 4 raiz raiz 4096 22 de novembro 13: 11… -rw-------. 1 EGDOC EGDOC 11885 3 09:35 .Bash_history -rw-r-r--. 1 Egdoc Egdoc 18 de julho 27 15:10 .Bash_logout […] 

Todas as variáveis ​​existentes no ambiente do usuário podem ser usadas ao invocar uma concha como um processo intermediário: embora isso possa parecer útil, pode ser uma fonte de problemas, especialmente ao lidar com informações potencialmente perigosas, o que pode levar a injeções de concha. Executando um processo com shell = true , portanto, é desencorajado e deve ser usado apenas em casos seguros.



Especificando um tempo limite para um processo

Normalmente não queremos que os processos de comportamento indevido sejam executados para sempre em nosso sistema assim que eles forem lançados. Se usarmos o tempo esgotado parâmetro do correr função, podemos especificar uma quantidade de tempo em segundos que o processo deve levar para concluir. Se não for concluído nesse período de tempo, o processo será morto com um Sigkill sinal, que, como sabemos, não pode ser pego por um processo. Vamos demonstrá -lo gerando um processo de longa execução e fornecendo um tempo limite em segundos:

>>> Process = subprocesso.Run (['Ping', 'Google.com '], timeout = 5) ping google.com (216.58.206.46) 56 (84) bytes de dados. 64 bytes de MIL07S07-in-F14.1E100.rede (216.58.206.46): icmp_seq = 1 ttl = 113 tempo = 29.3 ms 64 bytes de LHR35S10-in-F14.1E100.rede (216.58.206.46): icmp_seq = 2 ttl = 113 tempo = 28.3 ms 64 bytes de LHR35S10-in-F14.1E100.rede (216.58.206.46): icmp_seq = 3 ttl = 113 tempo = 28.5 ms 64 bytes de LHR35S10-in-F14.1E100.rede (216.58.206.46): icmp_seq = 4 ttl = 113 tempo = 28.5 ms 64 bytes de LHR35S10-in-F14.1E100.rede (216.58.206.46): icmp_seq = 5 ttl = 113 tempo = 28.1 MS Traceback (chamada mais recente): Arquivo "", linha 1, no arquivo "/usr/lib64/python3.9/Subprocesso.py ", linha 503, no stdout de corrida, stderr = processo.Comunique (entrada, tempo limite = timeout) Arquivo "/usr/lib64/python3.9/Subprocesso.py ", linha 1130, em comunicar stdout, stderr = self._Communicate (entrada, Endtime, Timeout) Arquivo "/usr/lib64/python3.9/Subprocesso.py ", linha 2003, em _comunicate self.Espere (tempo limite = eu._remaining_time (final)) arquivo "/usr/lib64/python3.9/Subprocesso.py ", linha 1185, em espera devolver._wait (timeout = timeout) arquivo "/usr/lib64/python3.9/Subprocesso.py ", linha de 1907, em _wait Raise timeoutExpired (self.args, timeout) subprocesso.TimeoutExpired: Command '[' Ping ',' Google.com ']' 'tempo seguido depois de 4.999826977029443 segundos 

No exemplo acima, lançamos o ping comando sem especificar uma quantidade fixa de Solicitação de eco pacotes, portanto, poderia correr para sempre. Também especificamos um tempo limite de 5 segundos através do tempo esgotado parâmetro. Como podemos observar o programa inicialmente executado, mas o TimeoutExpired a exceção foi levantada quando a quantidade especificada de segundos foi alcançada e o processo foi morto.

As funções de chamada, check_output e check_call

Como dissemos antes, o correr A função é a maneira recomendada de executar um processo externo e deve cobrir a maioria dos casos. Antes de ser introduzido em Python 3.5, as três principais funções de API de alto nível usadas para lançar um processo foram chamar, check_output e check_call; Vamos vê -los brevemente.

Primeiro de tudo, o chamar função: é usado para executar o comando descrito pelo args parâmetro; espera que o comando seja concluído e retorna seu Código de retorno. Corresponde aproximadamente ao uso básico do correr função.

O check_call o comportamento da função é praticamente o mesmo do correr função quando o verificar O parâmetro está definido como Verdadeiro: ele executa o comando especificado e espera que ele seja concluído. Se seu status existe não for 0, a ChamadoProcessError Exceção é levantada.

finalmente, o check_output função: funciona de maneira semelhante a check_call, mas retorna a saída do programa: ele não é exibido quando a função é executada.

Trabalhando em um nível mais baixo com a classe Popen

Até agora, exploramos as funções de API de alto nível no módulo de subprocesso, especialmente correr. Todas essas funções, sob o capô, interagem com o Popen aula. Por causa disso, na grande maioria dos casos, não precisamos trabalhar com isso diretamente. Quando é necessária mais flexibilidade, criando Popen Objetos se tornam diretamente necessários.



Suponha, por exemplo, queremos conectar dois processos, recriando o comportamento de um "tubo" da concha. Como sabemos, quando transmitimos dois comandos no shell, a saída padrão de um no lado esquerdo do tubo (|) é usado como a entrada padrão daquele à direita (verifique este artigo sobre redirecionamentos de shell se quiser saber mais sobre o assunto). No exemplo abaixo, o resultado da tubulação dos dois comandos é armazenado em uma variável:

$ output = "$ (DMESG | Grep SDA)" 

Para emular esse comportamento usando o módulo de subprocesso, sem ter que definir o concha parâmetro para Verdadeiro Como vimos antes, devemos usar o Popen Classe diretamente:

DMESG = subprocesso.Popen (['dmesg'], stdout = subprocess.Tubo) grep = subprocesso.Popen (['grep', 'sda'], stdin = dmesg.stdout) dmesg.stdout.close () saída = grep.comunicate () [0] 

Para entender o exemplo acima, devemos lembrar que um processo começou usando o Popen Classe diretamente não bloqueia a execução do script, pois agora é esperado.

A primeira coisa que fizemos no trecho de código acima, foi criar o Popen objeto representando o DMESG processo. Nós definimos o stdout deste processo para subprocesso.CANO: Este valor indica que um tubo para o fluxo especificado deve ser aberto.

Nós criamos outra instância do Popen classe para o grep processo. No Popen Construtor, especificamos o comando e seus argumentos, é claro, mas aqui está a parte importante, definimos a saída padrão do DMESG processo a ser usado como entrada padrão (stdin = dmesg.stdout), então para recriar a concha
comportamento do tubo.

Depois de criar o Popen objeto para o grep Comando, fechamos o stdout fluxo do DMESG processo, usando o fechar() Método: isso, conforme declarado na documentação, é necessário para permitir que o primeiro processo receba um sinal Sigpipe. Vamos tentar explicar por que. Normalmente, quando dois processos são conectados por um tubo, se o da direita do tubo (grep em nosso exemplo) sair antes do que a esquerda (DMESG), o último recebe um Sigpipe
sinal (tubo quebrado) e, por padrão, se encerra.

Ao replicar o comportamento de um tubo entre dois comandos em Python, no entanto, há um problema: o stdout do primeiro processo é aberto tanto no script pai quanto na entrada padrão do outro processo. Assim, mesmo que o grep O processo termina, o tubo ainda permanecerá aberto no processo do chamador (nosso script); portanto, o primeiro processo nunca receberá o Sigpipe sinal. É por isso que precisamos fechar o stdout fluxo do primeiro processo em nosso
script principal depois de lançarmos o segundo.

A última coisa que fizemos foi chamar o comunicar() Método no grep objeto. Este método pode ser usado para passar opcionalmente a entrada para um processo; Ele aguarda o processo de rescisão e retorna uma tupla onde o primeiro membro é o processo stdout (que é referenciado pelo saída variável) e o segundo o processo stderr.

Conclusões

Neste tutorial, vimos a maneira recomendada de gerar processos externos com python usando o subprocesso módulo e o correr função. O uso dessa função deve ser suficiente para a maioria dos casos; Quando é necessário um nível mais alto de flexibilidade, é preciso usar o Popen classe diretamente. Como sempre, sugerimos dar uma olhada no
Documentação de subprocesso para uma visão geral completa da assinatura de funções e classes disponíveis em
o módulo.

Tutoriais do Linux relacionados:

  • Uma introdução à automação, ferramentas e técnicas do Linux
  • Mastering Bash Script Loops
  • Coisas para instalar no Ubuntu 20.04
  • Loops aninhados em scripts de basquete
  • Como usar o comando tcpdump no Linux
  • Mint 20: Melhor que o Ubuntu e o Microsoft Windows?
  • Manipulando a entrada do usuário em scripts bash
  • Tutorial de depuração do GDB para iniciantes
  • Coisas para fazer depois de instalar o Ubuntu 20.04 fossa focal linux
  • Como monitorar a atividade da rede em um sistema Linux