Subprocess - Pt. 2 - STDOUT e STDERR

In questa lezione utilizzeremo il modulo subprocess per catturare l'output di un processo avviato in modo da poterlo utilizzare in un secondo momento. A differenza di quanto fatto nella prima parte, stavolta utilizzeremo IDLE.

Importiamo il modulo subprocess:

>>> import subprocess

Come già testato nella prima parte della lezione, proviamo a lanciare un comando come ifconfig o ipconfig se vi trovate su Windows, e vediamo cosa succede.

>>> subprocess.run(["ifconfig"])
CompletedProcess(args=['ifconfig'], returncode=0)

Vi ricordate di questo messaggio? È l'exit status di cui abbiamo parlato nella prima parte, e returncode=0 ci dice che tutto è andato a buon fine. Eppure, dov'è l'output del comando? Magari starete pensando di provare a passare il secondo parametro, shell=True, giusto? Proviamo.

>>> subprocess.run("ifconfig",shell=True)
CompletedProcess(args='ifconfig', returncode=0)

Come vedete, la situazione non cambia. Proviamo ora ad avviare invece VLC Media Player, e vediamo se almeno questo riusciamo ad aprirlo.

>>> subprocess.run("vlc")

CompletedProcess(args='vlc', returncode=0)
# vlc si apre #

Funziona! Ma che sta succedendo allora? Come facciamo a vedere l'output di comandi come ifconfig? Diamo uno sguardo alla documentazione del modulo: tra i parametri passabili a subprocess abbiamo anche stdout. acronimo di Standard Output e rappresenta l'eventuale output del sottoprocesso che istanziamo grazie al modulo subprocess.

Facciamo chiarezza. Funzioni come subprocess.run() vengono normalmente lanciate dall'interno di uno script, eseguito a sua volta da una shell di sistema. In quei casi, il sottoprocesso lanciato con run() eredita automaticamente lo standard output. Significa che qualsiasi cosa il sottoprocesso debba mostrarci in output, ci viene mostrata assieme al resto dell'output nella shell di sistema da cui abbiamo lanciato lo script.

Facciamo un esempio, io ho qui preparato un semplice script; proviamo ad eseguirlo:

#! /usr/bin/env python3
import subprocess
subprocess.run(["ifconfig"])

>>> python ./subzero.py
# output ifconfig #

Ed ecco che l'output ci viene ora mostrato di nuovo, come nella prima parte della lezione.

Ma torniamo allora su IDLE. Quindi come mai non vediamo l'output qui?

A differenza di lanciare il sottoprocesso direttamente dalla shell di sistema, qui succede che IDLE ha in sostanza due processi separati per gestire l'interfaccia grafica e per eseguire il codice.

Per dirla in parole povere, lo standard output non va da nessuna parte, e quindi non lo vediamo. Per poterlo visualizzare dobbiamo effettuare in maniera esplicita, una specie di collegamento tramite il parametro stdout, in questa maniera:

>>> subprocess.run(["ifconfig"], stdout=subprocess.PIPE)
# output in bytes code #

Ecco che ora ci viene finalmente restituito l'output! Anche se è un output piuttosto disorganizzato, c'è da dire che stiamo facendo progressi.

Stesso discorso vale per eventuali messaggi d'errore; come possiamo leggere nella documentazione del modulo, possiamo ottenerli comunque definiendo stderr. Proviamo ad innescare un errore e vedere cosa succede.

>>> subprocess.run(["ifconfigb"], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

CompletedProcess(args=['ifconfigb'], returncode=127, stdout=b'', stderr=b'/bin/sh: ifconfiga: command not found\n')

Ecco che otteniamo anche il messaggio d'errore.

Per quanto già così siamo ora in grado di analizzare l'output, c'è un ultimo accorgimento che possiamo fare per renderlo definitivamente human friendly. Come notate dalle b'', stdout è ora di tipo bytes object.

>>> sottoprocesso = subprocess.run(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

>>> type(sottoprocesso.stdout)
< class 'bytes' >

Possiamo aggiungere un ultimo parametro per la codifica, encoding, in modo da poterlo poi stampare in maniera leggibile successivamente:

>>> sottoprocesso = subprocess.run(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8")

>>> type(sottoprocesso.stdout)
< class 'str' >

>>> print(sottoprocesso.stdout)
# output human friendly #

Possiamo anche salvare il tutto in una variabile:

>>> sub_out = sottoprocesso.stdout

>>> print(sub_out)
# output human friendly #

Volendo, potete anche salvare l'output del comando, o l'eventuale errore, in un file testuale, in questo modo:

>>> log_out = open("log_output.txt", "w")
>>> subrocess.run(["ifconfig"], stdout=log_out)
>>> log_out.close()