subprocess - Genera Processi Addizionali
Scopo: Fa partire e comunica con processi addizionali
Il modulo subprocess supporta tre API per lavorare con processi. La funzione run()
, aggiunta in Python 3.5 è una API ad alto livello per eseguire un processo con opzione per catturare il suo output. Le funzioni call()
, check_call()
, e check_output()
sono tre precedenti API di alto livello, portate dalla versione 2 di Python. Esse sono ancora supportate e largamente usate in programmi esistenti. La classe Popen
è una API di basso livello usata per costruire le altre API e utile per interazioni di processo più complesse. Il costruttore per Popen
riceve argomenti per impostare il nuovo processo in modo che il genitore possa comunicare con esso tramite pipe. Fornisce tutte le funzionalità degli altri moduli e funzioni che rimpiazza, e altro ancora. L'API è consistente per tutti gli utilizzi, e molti dei passi supplementari necessari (tipo la chiusura di descrittori di file extra e l'assicurare che le pipe siano chiuse) sono "incorporate" invece che essere gestite dal codice dell'applicazione separatamente.
Il modulo subprocess è concepito per sostituire funzioni tipo os.system()
, os.spawn()
, le varianti di popen()
nei moduli os e popen2, così come il modulo commands. Per facilitare il confronto di subprocess con questi altri moduli, molti degli esempi di questa sezione ricreano quelli usati per os e popen2.
Eseguire un Comando Esterno
Per eseguire un comando esterno senza interagire con esso, proprio come si farebbe con os.system(), si usa la funzione run()
.
# subprocess_os_system.py
import subprocess
completed = subprocess.run(['ls', '-1'])
print('returncode:', completed.returncode)
Gli argomenti di riga di comando sono passati come lista di stringhe, che consente di evitare l'escape di apici o altri caratteri speciali che potrebbero essere interpretati dalla shell. run()
ritorna una istanza di CompletedProcess
con informazioni circa il processo tipo il codice di uscita e l'output.
$ python3 subprocess_os_system.py index.rst interaction.py repeater.py signal_child.py signal_parent.py subprocess_check_output_error_trap_output.py subprocess_os_system.py subprocess_pipes.py subprocess_popen2.py subprocess_popen3.py subprocess_popen4.py subprocess_popen_read.py subprocess_popen_write.py subprocess_run_check.py subprocess_run_output.py subprocess_run_output_error.py subprocess_run_output_error_suppress.py subprocess_run_output_error_trap.py subprocess_shell_variables.py subprocess_signal_parent_shell.py subprocess_signal_setpgrp.py returncode: 0
Impostando l'argomento shell a True
, fa sì che subprocess generi un processo intermedio di shell dalla quale viene eseguito il comando. La modalità predefinita è di eseguire il comando direttamente.
# subprocess_shell_variables.py
import subprocess
completed = subprocess.run('echo $HOME', shell=True)
print('returncode:', completed.returncode)
Usando una shell intermedia implica che le variabili, i modelli glob e altre caratteristiche speciali della shell nella stringa di comando sono elaborate prima che il comando venga eseguito.
$ python3 subprocess_shell_variables.py /home/robby returncode: 0
run()
senza passare check=True
equivale alla chiamata di call()
, che restituisce solo il codice di uscita dal processo.Gestione degli Errori
L'attributo returncode
di CompletedProcess
è il codice di uscita del programma. Il chiamante è responsabile dell'interpretazione per rilevare errori. Se l'argomento check
di run()
è True
, il codice di uscita viene verificato e, qualora indichi che un errore si è verificato, viene sollevata l'eccezione CalledProcessError
.
# subprocess_run_check.py
import subprocess
try:
subprocess.run(['false'], check=True)
except subprocess.CalledProcessError as err:
print('ERRORE:', err)
Il comando false
esce sempre con un codice di stato diverso da zero, che viene interpretato da run()
come un errore.
$ python3 subprocess_run_check.py ERRORE: Command '['false']' returned non-zero exit status 1
check=True
a run()
equivale alla chiamata di check_call()
.Catturare Output
I canali standard di input e output per il processo fatto partire da run()
sono legati all'input e output del genitore. Il che significa che il programma chiamante non può catturare l'output del comando. Si passi PIPE
per gli argomenti di stdout
e stderror
per catturare l'output per una successiva elaborazione.
# subprocess_run_output.py
import subprocess
completed = subprocess.run(
['ls', '-1', '/home/robby/test'],
stdout=subprocess.PIPE,
)
print('returncode:', completed.returncode)
print('Ci sono {} byte in stdout:\n{}'.format(
len(completed.stdout),
completed.stdout.decode('utf-8'))
)
Il comando ls -1 /home/robby/test
viene eseguito con successo, quindi il testo che stampa verso l'output standard viene catturate e restituito.
$ python3 subprocess_run_output.py returncode: 0 Ci sono 299 byte in stdout: subprocess_os_system.py subprocess_pipes.py subprocess_popen2.py subprocess_popen3.py subprocess_popen4.py subprocess_popen_read.py subprocess_popen_write.py subprocess_run_check.py subprocess_run_output.py subprocess_shell_variables.py subprocess_signal_parent_shell.py subprocess_signal_setsid.py
check=True
e impostando stdout
a PIPE
equivale a usare check_output
.L'esempio successivo esegue una serie di comandi in una sub-shell. I messaggi sono inviati allo standard output e allo standard error prima che i comandi escano con un codice di errore.
# subprocess_run_output_error.py
import subprocess
try:
completed = subprocess.run(
'echo to stdout; echo to stderr 1>&2; exit 1',
check=True,
shell=True,
stdout=subprocess.PIPE,
)
except subprocess.CalledProcessError as err:
print('ERRORE:', err)
else:
print('returncode:', completed.returncode)
print('Ci sono {} byte in stdout: {!r}'.format(
len(completed.stdout),
completed.stdout.decode('utf-8'))
)
Il messaggio allo standard error viene stampato sulla console, ma il messaggio verso lo standard output viene nascosto.
$ python3 subprocess_run_output_error.py to stderr ERRORE: Command 'echo to stdout; echo to stderr 1>&2; exit 1' returned non-zero exit status 1
Per evitare la scrittura sulla console di messaggi di errore dai comandi eseguiti tramite run()
, si imposti il parametro stderr
alla costante PIPE
.
# subprocess_run_output_error_trap.py
import subprocess
try:
completed = subprocess.run(
'echo to stdout; echo to stderr 1>&2; exit 1',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except subprocess.CalledProcessError as err:
print('ERRORE:', err)
else:
print('returncode:', completed.returncode)
print('Ci sono {} byte in stdout: {!r}'.format(
len(completed.stdout),
completed.stdout.decode('utf-8'))
)
print('Ci sono {} byte in stderr: {!r}'.format(
len(completed.stderr),
completed.stderr.decode('utf-8'))
)
Questo esempio non imposta check=True
in modo che l'output del comando venga catturato e stampato.
$ python3 subprocess_run_output_error_trap.py returncode: 1 Ci sono 10 byte in stdout: 'to stdout\n' Ci sono 10 byte in stderr: 'to stderr\n'
Per catturare i messaggi di errore quanto si usa check_output()
, si imposta stderr
a STDOUT
, e i messaggi saranno combinati con il resto dell'output dal comando.
# subprocess_check_output_error_trap_output.py
import subprocess
try:
output = subprocess.check_output(
'echo to stdout; echo to stderr 1>&2',
shell=True,
stderr=subprocess.STDOUT,
)
except subprocess.CalledProcessError as err:
print('ERRORE:', err)
else:
print('Ci sono {} byte in output: {!r}'.format(
len(output),
output.decode('utf-8'))
)
L'ordine nell'output può variare a seconda di come venga applicato il buffering al canale standard output e di quanti dati siano stampati.
$ python3 subprocess_check_output_error_trap_output.py Ci sono 20 byte in output: 'to stdout\nto stderr\n'
Sopprimere l'Output
Nei casi dove l'output non dovrebbe essere mostrato o catturato, si usi DEVNULL
per sopprimere il canale di output. Questo esempio sopprime i canali di standard error e output.
# subprocess_run_output_error_suppress.py
import subprocess
try:
completed = subprocess.run(
'echo to stdout; echo to stderr 1>&2; exit 1',
shell=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except subprocess.CalledProcessError as err:
print('ERRORE:', err)
else:
print('returncode:', completed.returncode)
print('stdout è {!r}'.format(completed.stdout))
print('stderr è {!r}'.format(completed.stderr))
Il nome DEVNULL
proviene dal file speciale di dispositivo di Unix /dev/null
, che risponde con un carattere di fine file quando aperto in lettura e riceve ma ignora qualsiasi dato in input quando è aperto in scrittura.
$ python3 subprocess_run_output_error_suppress.py returncode: 1 stdout è None stderr è None
Lavorare Direttamente con le Pipe
Le funzioni run()
, check_call()
e check_output
sono wrapper attorno alla classe Popen
. L'uso diretto di Popen
fornisce maggior controllo sul modo nel quale il comando viene eseguito, e come i suoi canali di input e output sono elaborati. Ad esempio, passando diversi argomenti per stdin
, stdout
, e stderr
è possibile imitare le varianti di os.popen()
.
Comunicazione Unidirezionale con un Processo
Per eseguire un processo e leggere tutto il suo output, si imposti il valore di stdout
a PIPE
e si invochi communicate()
.
# subprocess_popen_read.py
import subprocess
print('lettura:')
proc = subprocess.Popen(
['echo', '"to stdout"'],
stdout=subprocess.PIPE,
)
stdout_value = proc.communicate()[0].decode('utf-8')
print('stdout:', repr(stdout_value))
Questo è simile al modo in cui popen()
funziona, a eccezione del fatto che la lettura è gestita internamente dall'istanza di Popen
.
$ python3 subprocess_popen_read.py lettura: stdout: '"to stdout"\n'
Per impostare una pipe per consentire al programma chiamante di scrivere dati, si imposta stdin
a PIPE
.
# subprocess_popen_write.py
import subprocess
print('scrittura:')
proc = subprocess.Popen(
['cat', '-'],
stdin=subprocess.PIPE,
)
proc.communicate('stdin: to stdin\n'.encode('utf-8'))
Per inviare una volta dati al canale standard input del processo, si passano i dati a communicate()
. E' come usare popen()
con modalità 'w'
.
$ python3 subprocess_popen_write.py scrittura: stdin: to stdin
Comunicazione Bidirezionale con un Processo
Per impostare una istanza di Popen
per leggere e scrivere allo stesso tempo, si usa una combinazione delle tecniche precedenti.
# subprocess_popen2.py
import subprocess
print('popen2:')
proc = subprocess.Popen(
['cat', '-'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
msg = 'attraverso stdin a stdout'.encode('utf-8')
stdout_value = proc.communicate(msg)[0].decode('utf-8')
print('passa attraverso:', repr(stdout_value))
Questo imposta la pipe per imitare popen2()
.
$ python3 -u subprocess_popen2.py popen2: passa attraverso: 'attraverso stdin a stdout'
Catturare l'Error Output
E' anche possibile seguire sia il canale per stdout
che per stderr
, così come farebbe popen3()
.
# subprocess_popen3.py
import subprocess
print('popen3:')
proc = subprocess.Popen(
'cat -; echo "to stderr" 1>&2',
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
msg = 'attraverso stdin a stdout'.encode('utf-8')
stdout_value, stderr_value = proc.communicate(msg)
print('passa attraverso:', repr(stdout_value.decode('utf-8')))
Leggere da stderr
funziona allo stesso modo di stdout
. Passare PIPE
indica a Popen
di attaccarsi al canale, mentre communicate()
legge tutti i dati da esso prima di ritornare.
$ python3 -u subprocess_popen3.py popen3: passa attraverso: 'attraverso stdin a stdout'
Combinare Output Error ed Output Standard
Per dirigere l'output error dal processo verso il suo canale di output standard, si usa STDOUT
per stderr
invece che PIPE
.
# subprocess_popen4.py
import subprocess
print('popen4:')
proc = subprocess.Popen(
'cat -; echo "to stderr" 1>&2',
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
msg = 'attraverso stdin a stdout\n'.encode('utf-8')
stdout_value, stderr_value = proc.communicate(msg)
print('output combinato:', repr(stdout_value.decode('utf-8')))
print('valore di stderr:', repr(stderr_value))
Combinare l'output in questo modo è simile a quello in cui popen4()
lavora.
$ python3 -u subprocess_popen4.py popen4: output combinato: 'attraverso stdin a stdout\nto stderr\n' valore di stderr: None
Connettere Segmenti di una Pipe
Comandi multipli possono essere connessi tra loro in una conduttura (pipeline) in modo simile a quello con il quale lavora una shell Unix, creando istanze separate di Popen
e concatenando i loro input e output. L'attributo stdout
di una istanza di Popen
viene usata come argomento per stdin
per il successivo comando nella conduttura, al posto della costante PIPE
. L'output viene letto dal gestore di stdout
per il comando finale nella conduttura.
# subprocess_pipes.py
import subprocess
cat = subprocess.Popen(
['cat', 'index.rst'],
stdout=subprocess.PIPE,
)
grep = subprocess.Popen(
['grep', '.. literalinclude::'],
stdin=cat.stdout,
stdout=subprocess.PIPE,
)
cut = subprocess.Popen(
['cut', '-f', '3', '-d:'],
stdin=grep.stdout,
stdout=subprocess.PIPE,
)
end_of_pipe = cut.stdout
print('File inclusi:')
for line in end_of_pipe:
print(line.decode('utf-8').strip())
L'esempio riproduce la riga di comando:
$ cat index.rst | grep ".. literalinclude" | cut -f 3 -d:
La pipeline legge il file sorgente in formato reStructuredText per questa sezione e trova tutte le righe che includono altri file, poi stampa i nomi dei file che sono inclusi.
$ python3 -u subprocess_pipes.py File inclusi: subprocess_os_system.py subprocess_shell_variables.py subprocess_popen_read.py subprocess_popen_write.py subprocess_popen2.py subprocess_popen3.py subprocess_popen4.py subprocess_pipes.py repeater.py interaction.py signal_child.py signal_parent.py subprocess_signal_parent_shell.py subprocess_signal_setsid.py
Interagire Con Un Altro Comando
Tutti gli esempi sopra riportati presuppongono una limitata interazione. Il metodo communicate()
legge tutto l'output e attende che il processo figlio esca prima di ritornare. E' anche possibile scrivere verso e leggere da singoli gestori di pipe usati dalla istanza di Popen
, mentre il programma è in esecuzione. Un semplice programma che legge dallo standard input e scrive verso lo standard output illustra questa tecnica.
script_repeater.py
viene usato come processo figlio nell'esempio di seguito. Legge da stdin
e scrive i valori verso stdout
, una riga alla volta fino a che l'input si esaurisce. Scrive anche un messaggio verso stderr
quando inizia e finisce, mostrando il tempo di vita del processo figlio.
# repeater.py
import sys
sys.stderr.write('repeater.py: inizio\n')
sys.stderr.flush()
while True:
next_line = sys.stdin.readline()
sys.stderr.flush()
if not next_line:
break
sys.stdout.write(next_line)
sys.stdout.flush()
sys.stderr.write('repeater.py: in uscita\n')
sys.stderr.flush()
Il successivo esempio di interazione usa i gestori di file stdin
ed stdout
che appartengono all'istanza di Popen
in modi diversi. Nel primo esempio, una sequenza di 5 numeri viene scritta allo stdin
del processo, e dopo ogni scrittura la successiva riga di output viene riletta. Nel secondo esempio, gli stessi 5 numeri sono scritti ma l'output viene letto in una sola volta usando communicate()
.
# interaction.py
import io
import subprocess
print('Una riga alla volta:')
proc = subprocess.Popen(
'python3 repeater.py',
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
stdin = io.TextIOWrapper(
proc.stdin,
encoding='utf-8',
line_buffering=True, # send data on newline
)
stdout = io.TextIOWrapper(
proc.stdout,
encoding='utf-8',
)
for i in range(5):
line = '{}\n'.format(i)
stdin.write(line)
output = stdout.readline()
print(output.rstrip())
remainder = proc.communicate()[0].decode('utf-8')
print(remainder)
print()
print('Tutto l\'output in una volta:')
proc = subprocess.Popen(
'python3 repeater.py',
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
stdin = io.TextIOWrapper(
proc.stdin,
encoding='utf-8',
)
for i in range(5):
line = '{}\n'.format(i)
stdin.write(line)
stdin.flush()
output = proc.communicate()[0].decode('utf-8')
print(output)
Le righe "repeater.py: in uscita"
si trovano in punti diversi nell'output per ogni tipo di ciclo:
$ python3 interaction.py Una riga alla volta: repeater.py: inizio 0 1 2 3 4 repeater.py: in uscita Tutto l'output in una volta: repeater.py: inizio repeater.py: in uscita 0 1 2 3 4
Segnalazioni Tra Processi
Gli esempi di gestione del processo per il modulo os comprendono una dimostrazione della segnalazione tra processi usando os.fork()
ed os.kill()
. Visto che ogni istanza di Popen
fornisce un attributo pid con l'identificativo del processo figlio, è possibile fare qualcosa di simile con subprocess. Il prossimo esempio combina due script. Questo processo figlio imposta un gestore di segnale per il segnale USR
.
# signal_child.py
import os
import signal
import time
import sys
pid = os.getpid()
received = False
def signal_usr1(signum, frame):
"Callback chiamato quando viene ricevuto un segnale"
global received
received = True
print('FIGLIO {:>6}: Ricevuto USR1'.format(pid))
sys.stdout.flush()
print('FIGLIO {:>6}: Impostazione del gestore di segnale'.format(pid))
sys.stdout.flush()
signal.signal(signal.SIGUSR1, signal_usr1)
print('FIGLIO {:>6}: In pausa in attesa del segnale'.format(pid))
sys.stdout.flush()
time.sleep(3)
if not received:
print('FIGLIO {:>6}: Segnale mai ricevuto'.format(pid))
Questo script viene eseguito come processo genitore. Fa partire signal_child.py
, poi invia il segnale USR1
.
# signal_parent.py
import os
import signal
import subprocess
import time
import sys
proc = subprocess.Popen(['python3', 'signal_child.py'])
print('GENITORE: In pausa prima di inviare il segnale...')
sys.stdout.flush()
time.sleep(1)
print('GENITORE: Segnalazione al figlio')
sys.stdout.flush()
os.kill(proc.pid, signal.SIGUSR1)
Il risultato è:
$ python3 signal_parent.py GENITORE: In pausa prima di inviare il segnale... FIGLIO 6981: Impostazione del gestore di segnale FIGLIO 6981: In pausa in attesa del segnale GENITORE: Segnalazione al figlio FIGLIO 6981: Ricevuto USR1
Gruppi di Processo / Sessioni
Se il processo creato da Popen
genera dei sotto processi, questi suoi figli non riceveranno alcuno dei segnali inviati al genitore, Questo significa che, quando si usa l'argomento shell
per Popen
, sarà difficile far terminare il comando fatto partire dalla shell inviandogli un SIGINT
o un SIGTERM
.
# subprocess_signal_parent_shell.py
import os
import signal
import subprocess
import tempfile
import time
import sys
script = '''#!/bin/sh
echo "Shell script in esecuzione $$"
set -x
python3 signal_child.py
'''
script_file = tempfile.NamedTemporaryFile('wt')
script_file.write(script)
script_file.flush()
proc = subprocess.Popen(['sh', script_file.name])
print('GENITORE : In pausa prima di segnalare {}...'.format(
proc.pid))
sys.stdout.flush()
time.sleep(1)
print('GENITORE : Segnalazione al figlio {}'.format(proc.pid))
sys.stdout.flush()
os.kill(proc.pid, signal.SIGUSR1)
time.sleep(3)
Il pid usato per inviare il segnale è diverso dal pid del figlio dello script della shell che è in attesa del segnale perchè in questo esempio, ci sono tre processi separati che interagiscono.:
- Il programma
subprocess_signal_parent_shell.py
- Il processo della shell Unix che sta eseguendo lo script creato dal programma python principale
- Il programma
signal_child.py
$ python3 subprocess_signal_parent_shell.py GENITORE : In pausa prima di segnalare 7374... Shell script in esecuzione 7374 + python3 signal_child.py FIGLIO 7375: Impostazione del gestore di segnale FIGLIO 7375: In pausa in attesa del segnale GENITORE : Segnalazione al figlio 7374 FIGLIO 7375: Segnale mai ricevuto
Per inviare segnali ai discendenti senza conoscere il loro id di processo si usa un gruppo di processi da associare ai figli in modo che possa essere inviata una segnalazione a tutti insieme. Il gruppo di processi viene creato con os.setpgrp()
, che imposta l'identificativo del gruppo di processo pari all'id del processo corrente. Tutti i processi figli ereditano il gruppo di processo dal proprio genitore, e visto che si dovrebbe impostare solo nella shell creata da Popen
e i suoi discendenti, non occorre chiamare os.setpgrp()
nello stesso processo dove è stato creato Popen
. Al contrario la funzione viene passata a Popen
come argomento di preexec_fn
in modo che possa essere eseguito dopo il fork()
all'interno del nuovo processo, prima che usi exec()
per eseguire la shell. Per inviare segnali all'intero gruppo di processo, usare os.killpg()
con il valore pid
dell'istanza di Popen
.
# subprocess_signal_setpgrp.py
import os
import signal
import subprocess
import tempfile
import time
import sys
def show_setting_prgrp():
print('Chiamata di os.setpgrp() da {}'.format(os.getpid()))
os.setpgrp()
print('Il gruppo di procsso è ora {}'.format(
os.getpid(), os.getpgrp()))
sys.stdout.flush()
script = '''#!/bin/sh
echo "Shell script in esecuzione $$"
set -x
python3 signal_child.py
'''
script_file = tempfile.NamedTemporaryFile('wt')
script_file.write(script)
script_file.flush()
proc = subprocess.Popen(
['sh', script_file.name],
preexec_fn=show_setting_prgrp,
)
print('GENITORE : In pausa prima di segnalare {}...'.format(
proc.pid))
sys.stdout.flush()
time.sleep(1)
print('GENITORE : Segnalazione del gruppo di processo {}'.format(
proc.pid))
sys.stdout.flush()
os.killpg(proc.pid, signal.SIGUSR1)
time.sleep(3)
La sequenza di eventi è.
- Il programma genitore istanzia
Popen
. - L'istanza di
Popen
esegue un fork di un nuovo processo - Il nuovo processo esegue
os.setpgrp()
. - IL nuovo processo esegue
exec()
per far partire la shell- - La shell esegue lo script
- La shell esegue un nuovo fork e quel processo esegue Python
- Python esegue
signal_child.py
. - Il programma genitore segnala il processo di gruppo usando il pid della shell.
- I processi della shell e Python ricevono il segnale.
- La shell ignora il segnale.
- Il processo Python che sta eseguendo
signal_child.py
chiama il gestore di segnale.
$ python3 subprocess_signal_setpgrp.py Chiamata di os.setpgrp() da 8117 Il gruppo di procsso è ora 8117 GENITORE : In pausa prima di segnalare 8117... Shell script in esecuzione 8117 + python3 signal_child.py FIGLIO 8118: Impostazione del gestore di segnale FIGLIO 8118: In pausa in attesa del segnale GENITORE : Segnalazione del gruppo di processo 8117 FIGLIO 8118: Ricevuto USR1
Conclusioni
Vedere anche:
- subprocess
- La documentazione della libreria standard per questo modulo.
- os
- Sebbene subprocess sostituisca molte di esse, le funzioni per lavorare con i processi contenute nel modulo os sono ancora largamente usate nel codice esistente.
- Unix Signals and Process Groups
- Una buona descrizione delle segnalazioni in Unix e come i gruppi di processi funzionano.
- signal
- Ulteriori dettagli circa l'uso del modulo signal.
- Advanced Programming in the UNIX(R) Environment
- Tratta il lavorare con processi multipli, tipo la gestione di segnali, la chiusura di descrittori di file duplicati, ecc.
- pipes
- Modelli di pipeline di comandi della shell Unix nella libreria standard