Subprocess - Pt. 1 - Processi di Sistema

In questa lezione vedremo come avviare nuovi processi di sistema con Python utilizzando il modulo subprocess incluso nella Standard Library. Si tratta di uno dei moduli più utilizzati e utili in assoluto, quindi se tutto va bene alla fine di questa lezione avrete tra le mani un nuovo e potente strumento di programmazione per i vostri script.

Un breve accenno sulla terminologia: quando parlo di avviare nuovi processi di sistema mi riferisco all'avviare programmi e funzionalità come se ci trovassimo nella shell stessa di Linux o di Windows o del Mac.

>>> vlc
>>> ifconfig

La lezione è divisa in due parti: in questa vedremo come utilizzare la funzione run() del modulo subprocess per lanciare nuovi sottoprocessi.

Una volta appresi questi concetti, entreremo nel dettaglio nella seconda parte vedendo come catturarne l'output per utilizzarlo poi in un secondo momento. Vi garantisco che tutto si dimostrerà molto più semplice di quanto ci si possa aspettare.

In questa puntata utilizzeremo Python non da IDLE come fatto finora, ma avviandolo dal terminale di sistema stesso. Vi basta scrivere python o python3 per avviarlo. Su Windows il comando python potrebbe darvi errore a seconda di come avete installato il pacchetto, in questo caso provate semplicemente py e dovrebbe funzionare.

Cominciamo importando il modulo subprocess. Importiamo anche os per spostarci nel sistema.

import os
import subprocess

os.chdir("/home/pymike/Desktop")
os.listdir()


La funzione run()

Per avviare i vari processi usiamo la funzione run(), che accetta un comando o una lista di comandi.

subprocess.run(["vlc"])

Proviamo ora ad aprire un video:

subprocess.run(["vlc", "video_da_lanciare.mp4"])

Possiamo anche avviare script in altri linguaggi di programmazione, come Ruby:

subprocess.run(["ruby", "hello_ruby.rb"])

Fate attenzione a quest'ultima riga che ci viene mostrata in output: "CompletedProcess(args=['ruby', 'hello_ruby.rb'], returncode=0)": abbiamo una lista di parametri del comando lanciato, e poi il returncode; questo codice, chiamato anche exit status, viene restituito dal sistema al termine di un processo: returncode=0 significa che tutto è filato liscio.

Proviamo ora un altro paio di comandi. Ad esempio, per avere informazioni sulle interfacce di rete (su Windows è ipconfig) ci basta fare:

>>> subprocess.run(["ifconfig"])

(... output ...)
CompletedProcess(args=['ifconfig'], returncode=0)

Su Linux possiamo anche richiamare comandi come superuser: in questo caso, qualora stessimo eseguendo lo script come utente normale, ci verrà richiesta la password di root:

>>> subprocess.run(["sudo", "pacman", "-Syu"])

# output
[sudo] password for pymike: 
:: Synchronizing package databases...

(... resto dell'output ...)
CompletedProcess(args=['sudo', 'pacman', '-Syu'], returncode=0)

La funzione run() accetta più di un parametro; uno di questi è shell, che può essere True o False, e di default è passato come False. Se passiamo shell=True, il comando passato verrà eseguito richiamando prima di tutto una nuova shell di sistema e quindi eseguendolo attraverso di essa: questo porta ad alcuni vantaggi ma anche ad alcuni svantaggi. Tra i vantaggi, possiamo sfruttare al 100% la potenza della shell di comando del sistema operativo, utilizzando tutto il set di caratteri speciali messo a disposizione da ciascun sistema, ad esempio:

>>> subprocess.run("ls -l ~/Desktop",shell=True)

(... output ...)
CompletedProcess(args='ls -l ~/Desktop', returncode=0)

Se non avessi impostato shell=True, avrei ottenuto errore in quanto il carattere ~ non sarebbe stato riconosciuto dalla funzione run(). Inoltre come vedete, stavolta ho passato tutte le istruzioni sotto forma di stringa, il che facilita l'invio dei comandi.

Lo svantaggio di questa tecnica è però che comporta grosse falle di sicurezza: se non si sta attenti si possono causare danni irreversibili al sistema, specialmente se il comando da eseguire viene lanciato da utenti diciamo curiosi oppure semplicemente inesperti:

>>> from subprocess import run
>>> filename = input("What file would you like to display?\n")

What file would you like to display?
non_existent; rm -rf / #
>>> run("cat " + filename, shell=True) # Uh-oh. This will end badly..

[esempio tratto dalla documentazione ufficiale]

Quindi il mio consiglio è: se possibile, evitate di usareshell=True, a meno che non siate voi gli unici ad utilizzare questo programma!