Python subprocess

O terminal da Ana tem centenas de programas prontos: ls lista arquivos, grep procura texto, sqlite3 abre bancos de dados, git guarda versões. Às vezes, o jeito mais rápido de um programa Python fazer alguma coisa é pedir a um desses programas que faça. O módulo subprocess ("subprocesso", um processo filho) serve para isso: rodar outro programa, esperar ele terminar, saber se deu certo e pegar o que ele escreveu.

Você vai aprender
  • A rodar um programa com subprocess.run, passando o comando como uma lista.
  • A ler o código de saída com returncode.
  • A pegar o que o programa escreveu com capture_output=True e text=True.
  • A fazer o Python reclamar quando o comando falha, com check=True, e a ler o CalledProcessError.
  • A pôr um limite de tempo (timeout), mandar dados para o programa (input) e montar a lista com shlex.split.
  • Quando (quase nunca) usar shell=True, e os erros reais: programa que não existe, comando como texto, o grep que "falha" sem falhar.
Esta é uma aula de terminal

O subprocess chama programas do seu computador. O editor do guia roda o Python dentro do navegador, onde não existem ls, grep nem outros programas para chamar: lá o subprocess não funciona. Por isso os exemplos desta aula aparecem em janelas de terminal, com o que o Ubuntu da Ana respondeu de verdade, e as caixas "Experimente" treinam só a parte que é Python puro: montar a lista do comando e interpretar a resposta. Para rodar os exemplos, crie os arquivos no seu computador e use python3 arquivo.py (aula Rodando um Programa).


A ideia: pedir um serviço a um colega

A Ana não faz tudo sozinha na padaria. Quando precisa de um bolo de casamento, ela liga para a Dona Rosa, que é especialista: diz exatamente o que quer ("bolo, três andares, de coco"), espera ficar pronto e recebe duas coisas de volta: o bolo e um "deu tudo certo" (ou "faltou coco, não deu"). Ela não precisa saber como a Dona Rosa trabalha.

Na padariaNo subprocess
Ligar para a Dona Rosasubprocess.run(…)
O pedido, item por itemA lista do comando: ["ls", "-1"]
Esperar ficar prontoO run espera o programa terminar
O bolo que voltaO que o programa escreveu: stdout
"Deu certo" ou "não deu"O código de saída: returncode (0 = deu certo)
O bilhete de reclamaçãoA saída de erros: stderr

Nos exemplos, a Ana está na pasta da lojinha, que tem estes arquivos (e os .py que ela vai criando):

farinha,12
ovos,0
leite,5
acucar,0

1. O primeiro subprocess.run

import subprocess

resultado = subprocess.run(["ls", "-1"])
print("Terminou com o código", resultado.returncode)
$ python3 rodar1.py
clientes.json
estoque.csv
rodar1.py
vendas.py
Terminou com o código 0
import subprocess
O módulo vem com o Python.
subprocess.run(…)
"Rode": inicia o programa, espera ele terminar e só depois segue para a linha de baixo.
["ls", "-1"]
O comando, como uma lista de textos: o nome do programa e cada argumento num item separado. É o mesmo que digitar ls -1 no terminal (o -1 pede um nome por linha).
resultado
O run devolve um objeto CompletedProcess ("processo concluído") com tudo o que aconteceu.
resultado.returncode
O código de saída do programa: 0 é "deu certo" (aula Os Módulos os e sys).

Os nomes dos arquivos apareceram na tela porque, por padrão, o programa chamado escreve direto no mesmo terminal do Python. O seu programa não "viu" esses nomes: só ficou sabendo do código 0. Para pegar o que o programa escreveu, é preciso pedir.


2. Pegando a saída: capture_output=True e text=True

import subprocess

resultado = subprocess.run(["ls", "-1"], capture_output=True)
print(resultado.stdout)
$ python3 rodar2.py
b'clientes.json\nestoque.csv\nrodar1.py\nrodar2.py\nvendas.py\n'

Com capture_output=True ("capturar a saída"), nada foi para a tela: o que o ls escreveu ficou guardado em resultado.stdout. Mas veio estranho: um b na frente e os \n à mostra. É um valor do tipo bytes, os bytes crus que o programa mandou, ainda sem virar texto. Para receber um texto normal (str), acrescente text=True:

import subprocess

resultado = subprocess.run(["ls", "-1"], capture_output=True, text=True)
print(repr(resultado.stdout))
arquivos = resultado.stdout.splitlines()
print("São", len(arquivos), "arquivos:", arquivos)
$ python3 rodar3.py
'clientes.json\nestoque.csv\nrodar1.py\nrodar2.py\nrodar3.py\nvendas.py\n'
São 6 arquivos: ['clientes.json', 'estoque.csv', 'rodar1.py', 'rodar2.py', 'rodar3.py', 'vendas.py']
capture_output=True
Guarda a saída normal em resultado.stdout e a de erros em resultado.stderr, em vez de mostrar na tela.
text=True
Converte os bytes em texto (str), usando a codificação do sistema (UTF-8 na Ana). Agora dá para usar os métodos de texto.
.splitlines()
Quebra o texto numa lista, uma linha por item (aula Métodos de Texto).

Um exemplo mais útil: quais produtos acabaram? O grep procura as linhas que terminam em ,0 (o $, para o grep, quer dizer "fim da linha"):

import subprocess

resultado = subprocess.run(
    ["grep", ",0$", "estoque.csv"],
    capture_output=True,
    text=True,
)
print("Código:", resultado.returncode)
print("Acabaram:")
print(resultado.stdout, end="")
$ python3 zerados.py
Código: 0
Acabaram:
ovos,0
acucar,0

Repare na lista: ",0$" foi escrito sem aspas extras. No terminal, você digitaria grep ',0$' estoque.csv, com as aspas simples para o terminal não mexer no $. Na lista do subprocess não tem terminal no meio: cada item já chega ao programa exatamente como está.

Este era só um exemplo

Ler um arquivo e procurar linhas, o Python faz sozinho (aula Lendo Arquivos), sem depender de o grep existir no computador. O subprocess vale a pena quando o programa de fora faz algo que o Python não faz fácil: o git, um conversor de vídeo, o sqlite3, um script de outra pessoa.


3. Quando o comando dá errado: returncode e stderr

A pasta fotos não existe. O que o subprocess conta quando o ls falha?

import subprocess

resultado = subprocess.run(["ls", "fotos"], capture_output=True, text=True)
print("Código:", resultado.returncode)
print("stdout:", repr(resultado.stdout))
print("stderr:", repr(resultado.stderr))
$ python3 falha.py
Código: 2
stdout: ''
stderr: "ls: não é possível acessar 'fotos': Arquivo ou diretório inexistente\n"

Nenhum erro do Python! O run fez a parte dele: rodou o ls e trouxe a resposta. Quem falhou foi o ls, e ele avisou do jeito dos programas de terminal: código 2 (diferente de zero) e a mensagem na saída de erros (stderr), em português, porque o ls segue o idioma do sistema. A saída normal veio vazia.

Por padrão, falha é silenciosa

Se você não olhar o returncode, o seu programa segue em frente como se tudo tivesse dado certo. É o erro mais comum com subprocess. Ou confira o código, ou use o check=True, logo abaixo.


4. check=True: falhou, para tudo

Com check=True ("conferir"), o próprio run olha o código de saída. Se não for 0, ele lança uma exceção, a CalledProcessError ("erro do processo chamado"):

import subprocess

subprocess.run(["ls", "fotos"], check=True)
print("Esta linha não roda.")
$ python3 falha2.py
ls: não é possível acessar 'fotos': Arquivo ou diretório inexistente
Traceback (most recent call last):
  File "/home/ana/loja/falha2.py", line 3, in <module>
    subprocess.run(["ls", "fotos"], check=True)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 578, in run
    raise CalledProcessError(retcode, process.args,
                             output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['ls', 'fotos']' returned non-zero exit status 2.
$ echo $?
1

Leia por partes, de cima para baixo:

ls: não é possível acessar 'fotos'…
A mensagem do próprio ls. Sem capture_output, ela foi direto para a tela.
Traceback (most recent call last):
Começa a mensagem de erro do Python.
File "/home/ana/loja/falha2.py", line 3
A linha do seu programa: o subprocess.run da linha 3.
File "/usr/lib/python3.14/subprocess.py", line 578, in run
Uma linha de dentro do módulo subprocess: é lá que fica o raise CalledProcessError(...) que lançou o erro. Você não mexe nesse arquivo; a causa está na sua linha.
subprocess.CalledProcessError
O tipo da exceção. Ela mora dentro do módulo, por isso o nome completo é subprocess.CalledProcessError.
Command '['ls', 'fotos']' returned non-zero exit status 2.
"O comando ['ls', 'fotos'] devolveu o código de saída 2, diferente de zero".
echo $?
O Python terminou com 1, como em toda exceção não tratada. O print da linha 4 não rodou.

Tratando o erro com try

Uma exceção se trata com try e except (aula try e except). O objeto do erro traz o comando, o código e, se você capturou a saída, a mensagem:

import subprocess

try:
    subprocess.run(["ls", "fotos"], capture_output=True, text=True, check=True)
except subprocess.CalledProcessError as erro:
    print("O comando falhou!")
    print("Comando:", erro.cmd)
    print("Código:", erro.returncode)
    print("Mensagem:", erro.stderr.strip())
$ python3 falha3.py
O comando falhou!
Comando: ['ls', 'fotos']
Código: 2
Mensagem: ls: não é possível acessar 'fotos': Arquivo ou diretório inexistente
AtributoNo CompletedProcess (resultado)No CalledProcessError (erro)
O comandoresultado.argserro.cmd
O código de saídaresultado.returncodeerro.returncode
A saída normalresultado.stdouterro.stdout
A saída de errosresultado.stderrerro.stderr

(stdout e stderr só têm conteúdo com capture_output=True; sem ele, valem None.)


5. Mais três opções: timeout, input e sys.executable

timeout: não esperar para sempre

O run espera o programa terminar. E se ele travar? timeout=2 diz "espere no máximo 2 segundos". O sleep 10 abaixo é um programa que só fica parado 10 segundos:

import subprocess

subprocess.run(["sleep", "10"], timeout=2)
$ python3 demora.py
Traceback (most recent call last):
  File "/home/ana/loja/demora.py", line 3, in <module>
    subprocess.run(["sleep", "10"], timeout=2)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 557, in run
    stdout, stderr = process.communicate(input, timeout=timeout)
                     ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 1221, in communicate
    stdout, stderr = self._communicate(input, endtime, timeout)
                     ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 2179, in _communicate
    self.wait(timeout=self._remaining_time(endtime))
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 1279, in wait
    return self._wait(timeout=timeout)
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 2076, in _wait
    raise TimeoutExpired(self.args, timeout)
subprocess.TimeoutExpired: Command '['sleep', '10']' timed out after 2 seconds

O traceback é comprido, mas segue a regra de sempre: a primeira linha File é a sua (linha 3 do demora.py); as outras são o caminho por dentro do módulo; a última diz o que houve. TimeoutExpired: "o tempo acabou", depois de 2 segundos. O run encerra o programa atrasado antes de lançar o erro. Dá para tratar com except subprocess.TimeoutExpired:.

input e sys.executable: conversar com outro programa Python

Um programa pode receber dados pela entrada padrão (o "teclado" dele). A Ana tem um soma.py que lê linhas do estoque e soma as quantidades:

import sys

total = 0
for linha in sys.stdin:
    total += int(linha.split(",")[1])
print("Itens no estoque:", total)

E outro programa que chama o soma.py, mandando o conteúdo do arquivo como se alguém tivesse digitado:

import subprocess
import sys

with open("estoque.csv", encoding="utf-8") as arquivo:
    conteudo = arquivo.read()

resultado = subprocess.run(
    [sys.executable, "soma.py"],
    input=conteudo,
    capture_output=True,
    text=True,
    check=True,
)
print("O soma.py respondeu:", resultado.stdout.strip())
$ python3 chama_python.py
O soma.py respondeu: Itens no estoque: 17
sys.executable
O caminho do Python que está rodando agora (aula os e sys). Melhor que escrever "python3": se você estiver num ambiente virtual, o filho usa o mesmo Python, com os mesmos pacotes.
input=conteudo
O texto que o programa filho vai ler na entrada padrão (com text=True, um str).
check=True
Se o soma.py quebrar, o pai fica sabendo na hora.

(12 + 0 + 5 + 0 = 17.) Na vida real, para usar o código de outro arquivo Python, o normal é importar (aula Criando seu Módulo). Chamar com subprocess faz sentido quando o outro programa precisa rodar separado, como um programa à parte.


6. Montando a lista: shlex.split e o shell=True

Escrever ["grep", ",0$", "estoque.csv"] à mão cansa. Se você já tem o comando do jeito que digitaria no terminal, o módulo shlex (de shell lexer, "separador do terminal") quebra o texto do mesmo jeito que o terminal quebraria, respeitando as aspas:

import shlex

print(shlex.split("grep -c ',0$' estoque.csv"))
print(shlex.split('ls -l "Área de trabalho"'))
$ python3 dividir.py
['grep', '-c', ',0$', 'estoque.csv']
['ls', '-l', 'Área de trabalho']

As aspas sumiram e "Área de trabalho" ficou num item só, como o terminal faria. Um simples .split() quebraria nos espaços e erraria. O shlex é Python puro, então dá para treinar aqui:

Missão: o .split() quebrou "Área de trabalho" em três pedaços, e as aspas vieram junto. Troque por shlex.split(comando) para a lista sair ['ls', '-l', 'Área de trabalho'], pronta para o subprocess.run.

import shlex

comando = 'ls -l "Área de trabalho"'
partes = comando.split()
print(partes)
print(len(partes), "partes")
import shlex

comando = 'ls -l "Área de trabalho"'
partes = shlex.split(comando)
print(partes)
print(len(partes), "partes")

shell=True: deixar o terminal interpretar

Com shell=True, você passa um texto, e quem lê é um terminal (o /bin/sh), com todos os poderes dele: *, |, >…

import subprocess

resultado = subprocess.run("ls *.csv | wc -l", shell=True, capture_output=True, text=True)
print("Arquivos .csv:", resultado.stdout.strip())
$ python3 concha.py
Arquivos .csv: 1
Evite o shell=True com dados de fora

Se uma parte do texto vier do usuário (um input(), um argumento, um nome de arquivo), alguém pode escrever bolo; rm -rf ~, e o terminal vai obedecer aos dois comandos: o ; separa comandos. Com a lista, isso não acontece: "bolo; rm -rf ~" chega ao programa como um argumento só, um nome estranho, e nada mais. Prefira sempre a lista; use shell=True só com textos fixos, escritos por você, quando precisar mesmo de | ou *.


Formas diferentes de rodar um comando

JeitoQuando usar
subprocess.run([...])Só rodar e deixar a saída ir para a tela
subprocess.run([...], capture_output=True, text=True)Você quer ler o que o programa respondeu
… check=TrueQuase sempre: falhou, você fica sabendo
… timeout=NO programa pode travar (rede, programa de outra pessoa)
subprocess.run("…", shell=True)Só com texto fixo e quando precisa de |, *, >
os.system("…")Evite: é a forma antiga, com os riscos do shell=True e sem como pegar a saída
Fazer em Python mesmoSempre que der: ler arquivos, listar pastas (os.listdir), copiar (shutil). Funciona em qualquer sistema

Quando dá errado

O programa não existe: FileNotFoundError
import subprocess

subprocess.run(["lss", "-1"])
$ python3 errado1.py
Traceback (most recent call last):
  File "/home/ana/loja/errado1.py", line 3, in <module>
    subprocess.run(["lss", "-1"])
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 555, in run
    with Popen(*popenargs, **kwargs) as process:
         ~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 1039, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
    ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                        pass_fds, cwd, env,
                        ^^^^^^^^^^^^^^^^^^^
    ...<5 lines>...
                        gid, gids, uid, umask,
                        ^^^^^^^^^^^^^^^^^^^^^^
                        start_new_session, process_group)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 1990, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'lss'

Diferente do ls fotos: ali o ls rodou e falhou (código 2). Aqui, o programa lss nem existe, então não há código de saída nenhum, e o Python lança FileNotFoundError ("arquivo não encontrado") com o nome que ele procurou: 'lss'. O ...<5 lines>... é o Python resumindo um trecho comprido do código interno. Como consertar: confira o nome do programa (ls) e se ele está instalado (which ls no terminal).

O comando como um texto só, sem shell=True
import subprocess

subprocess.run("ls -1")
$ python3 errado2.py
Traceback (most recent call last):
  File "/home/ana/loja/errado2.py", line 3, in <module>
    subprocess.run("ls -1")
    ~~~~~~~~~~~~~~^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 555, in run
    with Popen(*popenargs, **kwargs) as process:
         ~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 1039, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
    ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                        pass_fds, cwd, env,
                        ^^^^^^^^^^^^^^^^^^^
    ...<5 lines>...
                        gid, gids, uid, umask,
                        ^^^^^^^^^^^^^^^^^^^^^^
                        start_new_session, process_group)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 1990, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'ls -1'

Olhe o nome no fim: 'ls -1', com o espaço. Sem terminal no meio, ninguém separou o texto, e o Python procurou um programa chamado, literalmente, "ls espaço traço um". Como consertar: passe a lista, ["ls", "-1"] (ou shlex.split("ls -1")).

O código diferente de zero que não é erro: o grep
import subprocess

resultado = subprocess.run(["grep", "chocolate", "estoque.csv"], capture_output=True, text=True)
print("Código:", resultado.returncode)
print("Achou:", repr(resultado.stdout))
$ python3 nada.py
Código: 1
Achou: ''

Nenhuma linha tem "chocolate", e o grep respondeu com o código 1. Para o grep, 1 não é falha: quer dizer "procurei e não achei" (o erro de verdade, como arquivo inexistente, é o 2). Se esse run tivesse check=True, um simples "não achei" virava CalledProcessError. Como lidar: leia a documentação do programa (man grep, no fim, explica os códigos) e, nesses casos, confira o returncode você mesmo, sem check=True.

Esqueceu o text=True

Você viu na seção 2: sem text=True, a saída vem como bytes (b'clientes.json\n…'), e métodos como .splitlines() devolvem bytes também; juntar com um texto normal dá TypeError. Como consertar: acrescente text=True.

No editor do guia

Rodar subprocess no "Experimente" não funciona: o Python do navegador não tem outros programas para chamar. O mesmo subprocess.run(["ls", "-1"]), numa caixa do editor, termina assim:

$ python3 main.py
Traceback (most recent call last):
  File "/home/ana/main.py", line 3, in <module>
    subprocess.run(["ls", "-1"])
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 554, in run
    with Popen(*popenargs, **kwargs) as process:
         ~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/subprocess.py", line 827, in __init__
    raise OSError(
        errno.ENOTSUP, f"{sys.platform} does not support processes."
    )
OSError: [Errno 138] emscripten does not support processes.

"O emscripten não tem processos": emscripten é a ferramenta que levou o Python para dentro do navegador, e lá não existe como iniciar outro programa. Não é erro no seu código. Rode esses exemplos no terminal do seu computador.

Para fechar: a parte de interpretar a resposta é Python puro, e é onde moram os erros mais sutis. A missão final treina isso com respostas que o grep poderia dar.


Referência rápida

CódigoO que faz
subprocess.run(["prog", "arg1", "arg2"])Roda o programa e espera terminar
resultado.returncodeCódigo de saída (0 = deu certo)
capture_output=TrueGuarda as saídas em .stdout e .stderr
text=TrueSaídas (e input) como texto, não bytes
check=TrueCódigo diferente de 0 vira subprocess.CalledProcessError
timeout=NMais de N segundos vira subprocess.TimeoutExpired
input="…"Manda o texto para a entrada padrão do programa
cwd="pasta"Roda o programa dentro de outra pasta
[sys.executable, "outro.py"]Roda outro script com o mesmo Python
shlex.split("cmd 'a b'")Texto → lista, como o terminal separaria
shell=TruePassa um texto para o /bin/sh interpretar (cuidado)
SintomaCausa provável
FileNotFoundError: … 'lss'O programa não existe ou o nome está errado
FileNotFoundError: … 'ls -1' (com espaço)Comando passado como texto: use a lista
CalledProcessError: … returned non-zero exit status NCom check=True, o programa terminou com código N
TimeoutExpired: … timed out after N secondsPassou do timeout
A saída começa com b'Faltou text=True
resultado.stdout é NoneFaltou capture_output=True
O programa falhou e o Python seguiu como se nada fosseNinguém olhou o returncode (ou faltou check=True)

Exercícios

1. Pegar a saída como texto

Complete as duas opções que fazem a saída do ls ficar em resultado.stdout, como texto:

resultado = subprocess.run(["ls"], =True, =True)

2. O código de saída

Qual atributo do resultado guarda o código de saída?

resultado.

3. A exceção do check=True

Complete o except:

except subprocess. as erro:

4. Qual destas chamadas roda grep -c ovos estoque.csv do jeito recomendado?

5. Sem check=True, o ls fotos falha (a pasta não existe). O que acontece no programa Python?

6. Por que evitar shell=True com um nome que o usuário digitou?

Missão final: entender a resposta do grep

Missão: a função interpretar recebe o returncode e o stdout de um grep ",0$" estoque.csv. Complete: código 0 → Acabaram: e os nomes dos produtos (o que vem antes da vírgula em cada linha), separados por vírgula; código 1 → Nada acabou.; qualquer outro → Erro no grep (código N)..

def interpretar(codigo, saida):
    print("Código recebido:", codigo)


interpretar(0, "ovos,0\nacucar,0\n")
interpretar(1, "")
interpretar(2, "")
def interpretar(codigo, saida):
    if codigo == 0:
        nomes = []
        for linha in saida.splitlines():
            nomes.append(linha.split(",")[0])
        print("Acabaram:", ", ".join(nomes))
    elif codigo == 1:
        print("Nada acabou.")
    else:
        print(f"Erro no grep (código {codigo}).")


interpretar(0, "ovos,0\nacucar,0\n")
interpretar(1, "")
interpretar(2, "")

No seu computador, troque as chamadas de teste por um subprocess.run(["grep", ",0$", "estoque.csv"], capture_output=True, text=True) e passe resultado.returncode e resultado.stdout para a função.


Resumo

  • subprocess.run(["programa", "arg"]) roda outro programa e espera ele terminar. Passe o comando como lista.
  • returncode é o código de saída: 0 deu certo; outro número, problema (ou, como no grep, "não achei").
  • capture_output=True guarda a saída em .stdout/.stderr; text=True faz dela um texto.
  • check=True transforma falha em subprocess.CalledProcessError, que se trata com try/except.
  • timeout limita a espera (TimeoutExpired); input manda dados; sys.executable chama outro script com o mesmo Python.
  • shlex.split monta a lista a partir do texto; shell=True só com texto fixo, nunca com dados do usuário.
  • Programa inexistente (ou comando como texto): FileNotFoundError. No editor do guia, o subprocess não funciona: use o terminal.