os - Accesso portabile alle funzionalità specifiche di un sistema operativo

Scopo Accesso portabile alle funzionalità specifiche di un sistema operativo
Versione Python 1.4 (o prececedente)

Il modulo os fornisce un incapsulamento per moduli specifici per una piattaforma tipo posix, nt e mac. L'API per le funzioni disponibile su tutte le piattaforme dovrebbe essere la stessa, quindi l'uso del modulo os offre una qualche garanzia di portabilità. Non tutte le funzioni sono disponibili in tutte le piattaforme, comunque. Molte delle funzioni di gestione dei processi qui descritte non sono disponibil in Windows.

La documentazione Python per il modulo os è sottotitolata "Interfacce varie per sistemi operativi". Il modulo comprende per lo più funzioni per creare e gestire processi in esecuzione oppure contenuto di filesystem (file e direcotry), con poche altre funzionalità qui e là.

Alcuni esempi di codice qui sotto funzioneranno solo su sistemi operativi tipo Unix.

Proprietario del processo

Il primo gruppo di funzioni da trattare sono usate per determinare e modificare gli identificativi del proprietario di un processo. Esse sono prevalentemente utitili per chi deve programmare demoni o programmi speciali di sistema i quali necessitano la modifica del livello di permessi invece che l'esecuzione come root. Non si tenta qui di spiegare gli intricati dettagli della sicurezza su Unix, proprietari di processi, ecc. in questo breve esposto. Vedere la lista di Riferimenti qui sotto per maggiori dettagli.

Si inizia con uno script che mostra per un processo le vere ed effettive informazioni circa il gruppo e l'utente, quindi ne cambia i valori effettivi. E' simile a quello che un demone dovrebbe fare quando parte come root durante l'inizializzazione del sistema, e deve abbassare il livello di privilegi ed essere eseguito come diverso utente. Se si provano gli esempi si dovrebbero modificare i valodi di TEST_GID e TEST_UID per farli corrispondere a quelli del proprio utente.

import os

TEST_GID=501
TEST_UID=527

def show_user_info():
    print 'Utente effettivo :', os.geteuid()
    print 'Gruppo effettivo :', os.getegid()
    print 'Utente reale     :', os.getuid(), os.getlogin()
    print 'Gruppo reale     :', os.getgid()
    print 'Gruppi reali     :', os.getgroups()
    return

print 'PRIMA DELLA MODIFICA:'
show_user_info()
print

try:
    os.setegid(TEST_GID)
except OSError:
    print 'ERRORE: Impossibile cambiare il gruppo effettivo. Rieseguire come root.'
else:
    print 'GRUPPO MODIFICATO:'
    show_user_info()
    print

try:
    os.seteuid(TEST_UID)
except OSError:
    print "ERRORE: Impossibile cambiare l'utente effettivo. Rieseguire come root."
else:
    print 'MODIFICA UTENTE:'
    show_user_info()
    print

Quando eseguito come utente/gruppo 527/501 su sistemi OS X, si vede questo risultato

$ python os_process_user_example.py
PRIMA DELLA MODIFICA:
Utente effettivo : 527
Gruppo effettivo : 501
Utente reale     : 527 dhellmann
Gruppo reale     : 501
Gruppi reali     : [501, 101, 500, 102, 98, 80]

GRUPPO MODIFICATO:
Utente effettivo : 527
Gruppo effettivo : 501
Utente reale     : 527 dhellmann
Gruppo reale     : 501
Gruppi reali     : [501, 101, 500, 102, 98, 80]

MODIFICA UTENTE:
Utente effettivo : 527
Gruppo effettivo : 501
Utente reale     : 527 dhellmann
Gruppo reale     : 501
Gruppi reali     : [501, 101, 500, 102, 98, 80]

Si nota che i valori non vengono modificati. Visto che non si sta eseguendo come root, i processi che vengono lanciati non possono modificare i valori di proprietario effettivo. Se si tenta di impostare l'utente od il gruppo effettivo a qualsiasi altra cosa diversa da se stessi viene sollevato un errore OSError.

Si veda ora cosa accade se si esegue lo stesso script usando sudo per partire con i privilegi di root:

$ sudo python os_process_user_example.py
PRIMA DELLA MODIFICA:
Utente effettivo : 0
Gruppo effettivo : 0
Utente reale     : 0 dhellmann
Gruppo reale     : 0
Gruppi reali     : [0, 1, 2, 8, 29, 3, 9, 4, 5, 80, 20]

GRUPPO MODIFICATO:
Utente effettivo : 0
Gruppo effettivo : 501
Utente reale     : 0 dhellmann
Gruppo reale     : 0
Gruppi reali     : [501, 1, 2, 8, 29, 3, 9, 4, 5, 80, 20]

MODIFICA UTENTE:
Utente effettivo : 527
Gruppo effettivo : 501
Utente reale     : 0 dhellmann
Gruppo reale     : 0
Gruppi reali     : [501, 1, 2, 8, 29, 3, 9, 4, 5, 80, 20]

In questo caso, visto che si è partiti come root, si possono modificare utente e gruppo effettivi per il processo. Una volta che l'UID effettivo è stato cambiato, il processo è limitato ai permessi per quell'utente. Visto che utenti non root non possono modificare i loro gruppi effettivi, occorre modificare prima il gruppo, poi l'utente.

Oltre al trovare e modificare il proprietario del processo, ci sono funzioni per determinare l'identificativo del processo corrente ed il suo genitore, per trovare e modificare gli identificativi di sessione e di gruppo del processo, così come trovare l'identificativo del terminale in controllo. Esse possono essere uteli per inviare segnali tra i processi o per applicazioni complesse tipo scrivere la propria shell di riga comandi.

Ambiente del processo

Un'altra caratteristica del sistema operativo a disposizione per i propri programmi attraverso il modulo os è l'ambiente. La variabili impostate nell'ambiente sono visibili come stringhe che possono essere lette tramite os.environ oppure os.getenv(). Le variabili d'ambiente sono in genere usate per valori di configurazione tipo percorsi di ricerca, locazioni di file, e flag di debug. Ecco un esempio per recuperare una variabile d'ambiente, e per passare un valore attraverso un processo figlio.

import os

print 'Valore iniziale:', os.environ.get('TESTVAR', None)
print 'Processo figlio:'
os.system('echo $TESTVAR')

os.environ['TESTVAR'] = "QUESTO VALORE E' STATO CAMBIATO"

print
print 'Valore cambiato:', os.environ['TESTVAR']
print 'Processo figlio:'
os.system('echo $TESTVAR')

del os.environ['TESTVAR']

print
print 'Valore rimosso :', os.environ.get('TESTVAR', None)
print 'Processo figlio:'
os.system('echo $TESTVAR')

L'oggetto os.environ segue la mappatura standard API di Python per recuperare ed impostare i valori. Le modifiche ad os.environ sono esportate per i processi figli.

$ python -u os_environ_example.py
Valore iniziale: None
Processo figlio:


Valore cambiato: QUESTO VALORE E' STATO CAMBIATO
Processo figlio:
QUESTO VALORE E' STATO CAMBIATO

Valore rimosso : None
Processo figlio:

Elaborare la Directory di Lavoro

La nozione di "directory di lavoro corrente" per un processo è un concetto che deriva da sistemi operativi con filesystem gerarchici. Questa è la directory nel filesystem che il processo usa come posizione iniziale quando i file sono indirizzati tramite percorsi relativi.

import os

print 'Partenza:', os.getcwd()
print os.listdir(os.curdir)

print 'Risalita di uno:', os.pardir
os.chdir(os.pardir)

print 'Dopo lo spostamento:', os.getcwd()
print os.listdir(os.curdir)

Si noti l'uso di os.curdir ed os.pardir per riferirsi alle directory attuale e genitore in maniera portabile. Il risultato non dovrebbe sorprendere:

$ python os_cwd_example.py
Partenza: /Users/dhellmann/Documents/PyMOTW/src/PyMOTW/os
['__init__.py', 'index.rst', 'os_access.py', 'os_cwd_example.py', 'os_directories.py', 'os_environ_example.py', 'os_exec_example.py', 'os_fork_example.py', 'os_kill_example.py', 'os_popen.py', 'os_popen2.py', 'os_popen2_seq.py', 'os_popen3.py', 'os_popen4.py', 'os_process_id_example.py', 'os_process_user_example.py', 'os_spawn_example.py', 'os_stat.py', 'os_stat_chmod.py', 'os_stat_chmod_example.txt', 'os_symlinks.py', 'os_system_background.py', 'os_system_example.py', 'os_system_shell.py', 'os_wait_example.py', 'os_waitpid_example.py', 'os_walk.py']
Risalita di un livello: ..
Dopo lo spostamento: /Users/dhellmann/Documents/PyMOTW/src/PyMOTW
['__init__.py', 'abc', 'about.rst', 'anydbm', 'array', 'articles', 'asynchat', 'asyncore', 'atexit', 'base64', 'BaseHTTPServer', 'bisect', 'builtins.rst', 'bz2', 'calendar', 'cc-by-nc-sa.png', 'cgitb', 'cmd', 'collections', 'commands', 'compileall', 'compression.rst', 'ConfigParser', 'contents.rst', 'contextlib', 'Cookie', 'copy', 'copyright.rst', 'cryptographic.rst', 'csv', 'data_types.rst', 'datetime', 'dbhash', 'dbm', 'decimal', 'dev_tools.rst', 'difflib', 'dircache', 'dis', 'docs', 'dumbdbm', 'EasyDialogs', 'exceptions', 'feed.png', 'file_access.rst', 'file_formats.rst', 'filecmp', 'fileinput', 'fnmatch', 'fractions', 'frameworks.rst', 'functools', 'gdbm', 'generic_os.rst', 'getopt', 'getpass', 'gettext', 'glob', 'grp', 'gzip', 'hashlib', 'heapq', 'history.rst', 'hmac', 'i18n.rst', 'imaplib', 'imp', 'importing.rst', 'inspect', 'internet_data.rst', 'internet_protocols.rst', 'ipc.rst', 'itertools', 'json', 'language.rst', 'linecache', 'locale', 'logging', 'mail.png', 'mailbox', 'markup.rst', 'mhlib', 'miscelaneous.rst', 'mmap', 'multiprocessing', 'numeric.rst', 'operator', 'optional_os.rst', 'optparse', 'os', 'ospath', 'pdf_contents.rst', 'persistence.rst', 'pickle', 'pipes', 'pkgutil', 'platform', 'plistlib', 'pprint', 'profile', 'profilers.rst', 'pwd', 'pyclbr', 'pydoc', 'Queue', 'readline', 'resource', 'rlcompleter', 'robotparser', 'runtime_services.rst', 'sched', 'shelve', 'shlex', 'shutil', 'signal', 'SimpleXMLRPCServer', 'smtpd', 'smtplib', 'SocketServer', 'string', 'string_services.rst', 'StringIO', 'struct', 'subprocess', 'sys', 'tabnanny', 'tarfile', 'tempfile', 'textwrap', 'threading', 'time', 'timeit', 'trace', 'traceback', 'unittest', 'unix.rst', 'urllib', 'urllib2', 'urlparse', 'uuid', 'warnings', 'weakref', 'webbrowser', 'whichdb', 'xml', 'xmlrpclib', 'zipfile', 'zipimport', 'zlib']

Pipe

Il modulo os fornisce diverse funzioni per gestire l'I/O di processi figli usando le pipe. Tutte le funzioni lavorano essenzialmente allo stesso modo, ma restituiscono diversi file handler a seconda del tipo di input od output desiderato. Per la maggior parte queste funzioni sono rese obsolete dal modulo subprocess (aggiunto in Python 2.4), ma ci sono buone probabilità di imbattersi in esse se si lavora al mantenimento di legacy code.

La funzione per pipe più comunemente usata è popen(). Essa crea un nuovo processo eseguendo il comando fornito ed attacca un singolo flusso all'input od all'output di quel processo, a seconda del parametro mode. Mentre le funzioni popen() funzionano su Windows, alcuni di questi esempi presuppongono l'uso di un qualche tipo di shell Unix. Anche le descrizioni dei flussi presuppongono una terminologia tipo Unix:

  • stdin - Il flusso "standard di input" per un processo (descrittore file 0) è leggibile dal processo. In genere è dove va l'input del terminale
  • stdout - Il flusso "standard di output" per un processo (descrittore file 1) è scrivibile dal processo, ed è usato per visualizzare il normale output all'utente.
  • stderr - Il flusso "standard di errore" per un processo (descrittore file 2) è scrivibile dal processo, e viene usato per convogliare messaggi di errore.
import os

print 'popen, lettura:'
pipe_stdout = os.popen('echo "allo stdout"', 'r')
try:
    stdout_value = pipe_stdout.read()
finally:
    pipe_stdout.close()
print '\tstdout:', repr(stdout_value)

print '\npopen, scrittura:'
pipe_stdin = os.popen('cat -', 'w')
try:
    pipe_stdin.write('\tstdin: allo stdin\n')
finally:
    pipe_stdin.close()
$ python -u os_popen.py
popen, lettura:
	stdout: 'allo stdout\n'

popen, scrittura:
	stdin: allo stdin

Il chiamante può solo leggere dal o scrivere sul flusso associato con il processo figlio, il che ne limita l'utilità. Le altre varianti di popen() forniscono flussi addizionali in modo che sia possibilie lavorare con stdin, stdout ed stderr secondo necessità.

Ad esempio, popen2() restituisce un flusso a sola lettura attaccato allo stdin del processo figlio, ed un flusso a sola lettura attaccato al suo stdout.

import os

print 'popen2:'
pipe_stdin, pipe_stdout = os.popen2('cat -')
try:
    pipe_stdin.write('dallo stdin allo stdout')
finally:
    pipe_stdin.close()
try:
    stdout_value = pipe_stdout.read()
finally:
    pipe_stdout.close()
print '\tpassa attraverso:', repr(stdout_value)

Il semplicistico esempio illustra la comunicazione bidirezionale. Il valore scritto allo stdin viene letto da cat (grazie al parametro '-'), quindi viene riscritto allo stdout. Ovviemente un processo più complicato potrebbe passare altri tipi di messaggio avanti e indietro attraverso la pipe; anche oggetti serializzati.

$ python -u os_popen2.py
popen2:
        passa attraverso: 'dallo stdin allo stdout'

Nella maggior parte dei casì si vorrebbe avere accesso ad entrambi stdout e stderr. Il flusso stdout viene usato per passare messaggi mentre stderr viene usato per gli errori, in modo che la lettura separata riduce la complessità dell'analisi di ogni messaggio di errore. La funzione popen3() restituisce 3 flussi aperti legati agli stdin, stdout ed stderr del nuovo processo.

import os

print 'popen3:'
pipe_stdin, pipe_stdout, pipe_stderr = os.popen3('cat -; echo ";allo stderr" 1>&2')
try:
    pipe_stdin.write('attraverso stdin a stdout')
finally:
    pipe_stdin.close()
try:
    stdout_value = pipe_stdout.read()
finally:
    pipe_stdout.close()
print '\tpassa attraverso:', repr(stdout_value)
try:
    stderr_value = pipe_stderr.read()
finally:
    pipe_stderr.close()
print '\tstderr:', repr(stderr_value)

Si noti che occorre leggere da, quindi chiudere entrambi stdout e stderr separatamente. Ci sono da tenere presente alcune cose riguardo al controllo del flusso ed alla sequenzialità quando si ha a che fare con l'I/O di processi multipli. L'I/O viene tenuto in una memoria tampone, e se il chiamante si aspetta di riuscire a leggere tutti i dati da un flusso, allora il processo figlio deve chiudere quel flusso per indicare la fine del file. Per ulteriori informazioni circa queste problematiche fare riferimento alla sezione Flow Control Issues della documentazione della libreria Python.

$ python -u os_popen3.py
popen3:
        passa attraverso: 'attraverso stdin a stdout'
	stderr: ';allo stderr\n'

Infine popen4() restituisce 2 flussi, stdin ed un stdout/stderr combinato. Utile quando i risultati del comando devono essere registrati ma non analizzati direttamente.

import os

print 'popen4:'
pipe_stdin, pipe_stdout_and_stderr = os.popen4('cat -; echo ";allo stderr" 1>&2')
try:
    pipe_stdin.write('attraverso stdin allo stdout')
finally:
    pipe_stdin.close()
try:
    stdout_value = pipe_stdout_and_stderr.read()
finally:
    pipe_stdout_and_stderr.close()
print '\toutput combinato:', repr(stdout_value)
$ python -u os_popen4.py
popen4:
        output combinato: 'attraverso stdin allo stdout;allo stderr\n'

Oltre ad accettare una singola stringa di comando da passare alla shell per l'elaborazione, popen2(), popen3() e popen4() accettano anche una sequenza di stringhe (comandi, seguiti da parametri). In questo caso, i parametri non sono elaborati dalla shell.

import os

print 'popen2, comandi in sequenza:'
pipe_stdin, pipe_stdout = os.popen2(['cat', '-'])
try:
    pipe_stdin.write('attraverso stdin allo stdout')
finally:
    pipe_stdin.close()
try:
    stdout_value = pipe_stdout.read()
finally:
    pipe_stdout.close()
print '\tpassa attraverso:', repr(stdout_value)
$ python -u os_popen2_seq.py
popen2, comandi in sequenza:
        passa attraverso: 'attraverso stdin allo stdout'

Descrittori di File

Il modulo os include un'insieme standard di funzioni per lavorare con i descrittori di file a basso livello (interi che rappresentano file aperti che appartengono al processo corrente). Si tratta di una API a basso livello fornita dagli oggetti file. Si sorvola sulla descrizione, qui, visto che in genere è più facile lavorare direttamente con gli oggetti file. Fare riferimento alla documentazione della libreria per dettagli se si vogliono usare i descrittori di file.

Permessi del Filesystem

La funzione os.access() può essere usata per verificare i diritti di accesso che un processo detiene per un file.

import os

print 'Verifica:', __file__
print 'Esiste:', os.access(__file__, os.F_OK)
print 'Leggibile:', os.access(__file__, os.R_OK)
print 'Scrivibile:', os.access(__file__, os.W_OK)
print 'Eseguibile:', os.access(__file__, os.X_OK)

I risultati dipendono da come viene installato il codice di esempio ma dovrebbe essere qualcosa come questo:

$ python -u os_access.py
Verifica: os_access.py
Esiste: True
Leggibile: True
Scrivibile: True
Eseguibile: False

La documentazione della libreria per os.access() include due avvertimenti speciali. Primo, non ha molto senso chiamare os.access() per verificare se un file può essere aperto prima di aprirlo effettivamente chiamando open() su di esso. C'è una piccola ma effettiva finestra temporale tra le due chiamate durante la quale i permessi per il file potrebbero mutare. L'altro avvertimento riguarda per la maggior parte i filesystem di rete che estendono le semantiche dei permessi POSIX. Alcuni tipi di filesystem potrebbero rispondere alla chiamata POSIX che un processo ha il permesso di accedere ad un file, quindi riportare un fallimento quando il tentativi viene fatto usando open() per una qualche ragione non verificato attraverso la chiamata POSIX. Tutto sommato è meglio chiamare open() con la modalità richiesta e catturare l'IOError sollevato se ci fosse un problema.

Informazioni pià dettagliate circa un file possono essere ottenute tramite os.stat() oppure os.lstat() (Se si vuole lo status di qualcosa che potrebbe essere un link simbolico)

import os
import sys
import time

if len(sys.argv) == 1:
    filename = __file__
else:
    filename = sys.argv[1]

stat_info = os.stat(filename)

print 'os.stat(%s):' % filename
print '\tDimensione:', stat_info.st_size
print '\tPermessi:', oct(stat_info.st_mode)
print '\tProprietario:', stat_info.st_uid
print '\tDispositivo:', stat_info.st_dev
print '\tUltima modifica:', time.ctime(stat_info.st_mtime)

Ancora una volta, i risultati potrebbero variare in base a come è stato installato il codice di esempio. Si provi a passare nomi di file diversi da riga comandi a os_stat.py.

$ python os_stat.py
os.stat(os_stat.py):
	Dimensione: 453
	Permessi: 0100644
	Proprietario: 1000
	Dispositivo: 2054
	Ultima modifica: Sat Jul  3 14:59:27 2010

Su sistemi tipo Unix i permessi per i file possono esssere cambiati usando os.chmod(), passando la modalità come intero. I valori delle modalità possono essere costruiti usando le costanti definite nel modulo stat. Ecco un esempio che modifica il bit del permesso di esecuzione per l'utente.

import os
import stat

filename = 'os_stat_chmod_example.txt'
if os.path.exists(filename):
    os.unlink(filename)
f = open(filename, 'wt')
f.write('contenuto')
f.close()

# Determina quali permessi sono già impostati usando  stat
existing_permissions = stat.S_IMODE(os.stat(filename).st_mode)

if not os.access(filename, os.X_OK):
    print 'Aggiunta dei permessi di esecuzione'
    new_permissions = existing_permissions | stat.S_IXUSR
else:
    print 'Rimozione  dei permessi di esecuzione'
    # usa xor per rimuovere il permesso di esecuzione per l'utente
    new_permissions = existing_permissions ^ stat.S_IXUSR

os.chmod(filename, new_permissions)

Lo script assume in prima battuta che si abbiano i permessi necessari per modificare la modalità.

$ python os_stat_chmod.py
Aggiunta dei permessi di esecuzione

Directory

Ci sono parecchie funzioni per lavorare con le directory nel filesystem, incluse quelle per crearle, elencarne il contenuto e rimuoverle.

import os

dir_name = 'os_directories_example'

print 'Creazione', dir_name
os.makedirs(dir_name)

file_name = os.path.join(dir_name, 'esempio.txt')
print 'Creazione', file_name
f = open(file_name, 'wt')
try:
    f.write('file di esempio')
finally:
    f.close()

print 'Elenco di', dir_name
print os.listdir(dir_name)

print 'Pulizia'
os.unlink(file_name)
os.rmdir(dir_name)
$ python os_directories.py
Creazione os_directories_example
Creazione os_directories_example/esempio.txt
Elenco di os_directories_example
['esempio.txt']
Pulizia

Ci sono due insiemi di funzioni per creare ed elimiare directory. Quando si creano nuove directory con os.mkdir(), tutte le directory genitrici devono già esistere. Quando si elimina una directory con os.rmdir(), solo la directory rappresentata dall'ultima parte del percorso viene in realtà rimossa. Al contrario os.makedirs() ed os.removedirs() operano su tutti i nodi del percorso. os.makedirs() creerà qualsiasi parte del percorso che non esista, ed os.removedirs() eliminerà tutte le directory genitrici (a patto che possa).

Link Simbolici

Su piattaforme e filesystem che li supportano, ci sono anche funzioni che operano sui link simbolici.

import os, tempfile

link_name = tempfile.mktemp()

print 'Creazione del link %s -> %s' % (link_name, __file__)
os.symlink(__file__, link_name)

stat_info = os.lstat(link_name)
print 'Permissi:', oct(stat_info.st_mode)

print 'Punta a:', os.readlink(link_name)

# Pulizia
os.unlink(link_name)

Sebbene os includa os.tempnam() per la creazione di nomi di file temporanei, esso non è così sicuro come il modulo tempfile e produce un messaggio RuntimeWarning quando viene usato. In generale è meglio usare tempfile come in questo esempio:

$ python os_symlinks.py
Creazione del link /tmp/tmp2yrV5_ -> os_symlinks.py
Permissi: 0120777
Punta a: os_symlinks.py

Attraversare un Albero di Directory

La funzione os.walk() attraversa una directory ricorsivamente e per ogni directory genera una tuple contenente il percorso della directory, qualasiasi immediata sotto directory di quel percorso ed i nomi di tutti i file in quella directory. Questo esempio mostra un elenco ricorsivo semplificato di directory.

import os, sys

# Se non viene passato un percorso da elencare, si usa /tmp
if len(sys.argv) == 1:
    root = '/tmp'
else:
    root = sys.argv[1]

for dir_name, sub_dirs, files in os.walk(root):
    print '\n', dir_name
    # Aggiunge ai nomi delle sotto direcotry una /
    sub_dirs = [ '%s/' % n for n in sub_dirs ]
    # Combina i contenuti delle directory assieme
    contents = sub_dirs + files
    contents.sort()
    # Mostra il contenuto
    for c in contents:
        print '\t%s' % c
/tmp
        .ICE-unix/
        .X0-lock
        .X11-unix/
        .font-unix/
        .s.PGSQL.5432
        .s.PGSQL.5432.lock
        apr37JbgJ
        ccc_exclude.vHRzyU
        com.hp.launchport
        emacs527/
        example.db
        launch-LwLyiB/
        launch-QoWYy9/
        launch-Y1GkS5/
        launchd-173.E4bnIY/
        pymotw_import_example.shelve
        ssh-2hdhfsgBgC/
        trace_example.recurse.cover
        var_backups/

/tmp/.font-unix

/tmp/.ICE-unix

/tmp/.X11-unix
        X0

/tmp/emacs527
        server

/tmp/launch-LwLyiB
        Listeners

/tmp/launch-QoWYy9
        Render

/tmp/launch-Y1GkS5
        :0

/tmp/launchd-173.E4bnIY
        sock

/tmp/ssh-2hdhfsgBgC

/tmp/var_backups

Eseguire Comandi Esterni

Molte di queste funzioni per lavorare con i processi hanno una portabilità limitata. Per un modo più consistente di lavorare con i processi con soluzioni indipendenti dalla piattoforma, fare invece riferimento al modulo subprocess.

Il mezzo più semplice per eseguire un comando separatamente, senza interagire con esso in alcun modo, è os.system(). Esso riceve una singola stringa che corrisponde alla riga di comando da eseguire da parte di un sub processo eseguendo una shell.

import os

# Semplice comando
os.system('ls -l')
$ python -u os_system_example.py
total 248
-rw-r--r--  1 dhellmann  dhellmann      0 Mar 22 17:03 __init__.py
-rw-r--r--  1 dhellmann  dhellmann  22122 Jun 27 11:34 index.rst
-rw-r--r--  1 dhellmann  dhellmann   1360 Mar 22 17:03 os_access.py
-rw-r--r--  1 dhellmann  dhellmann   1347 Mar 22 17:03 os_cwd_example.py
-rw-r--r--  1 dhellmann  dhellmann   1499 Mar 22 17:03 os_directories.py
-rw-r--r--  1 dhellmann  dhellmann   1573 Mar 22 17:03 os_environ_example.py
-rw-r--r--  1 dhellmann  dhellmann   1241 Mar 22 17:03 os_exec_example.py
-rw-r--r--  1 dhellmann  dhellmann   1267 Mar 22 17:03 os_fork_example.py
-rw-r--r--  1 dhellmann  dhellmann   1703 Mar 22 17:03 os_kill_example.py
-rw-r--r--  1 dhellmann  dhellmann   1476 Mar 22 17:03 os_popen.py
-rw-r--r--  1 dhellmann  dhellmann   1506 May  4 08:57 os_popen2.py
-rw-r--r--  1 dhellmann  dhellmann   1528 May  4 08:56 os_popen2_seq.py
-rw-r--r--  1 dhellmann  dhellmann   1658 May  4 08:57 os_popen3.py
-rw-r--r--  1 dhellmann  dhellmann   1567 May  4 08:57 os_popen4.py
-rw-r--r--  1 dhellmann  dhellmann   1395 Mar 22 17:03 os_process_id_example.py
-rw-r--r--  1 dhellmann  dhellmann   1896 May  4 08:28 os_process_user_example.py
-rw-r--r--  1 dhellmann  dhellmann   1206 Mar 22 17:03 os_spawn_example.py
-rw-r--r--  1 dhellmann  dhellmann   1516 Mar 22 17:03 os_stat.py
-rw-r--r--  1 dhellmann  dhellmann   1751 Mar 22 17:03 os_stat_chmod.py
-rwxr--r--  1 dhellmann  dhellmann      8 Jun 27 11:41 os_stat_chmod_example.txt
-rw-r--r--  1 dhellmann  dhellmann   1421 May  4 09:25 os_symlinks.py
-rw-r--r--  1 dhellmann  dhellmann   1250 Mar 22 17:03 os_system_background.py
-rw-r--r--  1 dhellmann  dhellmann   1191 Mar 22 17:03 os_system_example.py
-rw-r--r--  1 dhellmann  dhellmann   1214 Mar 22 17:03 os_system_shell.py
-rw-r--r--  1 dhellmann  dhellmann   1499 Mar 22 17:03 os_wait_example.py
-rw-r--r--  1 dhellmann  dhellmann   1555 Mar 22 17:03 os_waitpid_example.py
-rw-r--r--  1 dhellmann  dhellmann   1643 Mar 22 17:03 os_walk.py

Visto che il comando viene passato direttamente alla shell per essere elaborato, può anche comprendere sintassi di shell tipo globbing o variabili di ambiente.

import os

# Commndo con espansione della shell
os.system('ls -ld $TMPDIR')
$ python -u os_system_shell.py
drwx------  8 dhellmann  dhellmann  272 Jun 27 11:41 /var/folders/9R/9R1t+tR02Raxzk+F71Q50U+++Uw/-Tmp-/

A meno che non si esegua esplicitamente il comando in background, la chiamata ad os.system() è bloccante fino a quando non è completata. Gli standard input, output ed error dal processo figlio sono abbinati ai flussi appropriati che appartengono in modo predefinito al chiamante, ma possono essere rediretti usando sintassi di shell.

import os
import time

print 'Chiamata...'
os.system('date; (sleep 3; date) &')

print 'In pausa...'
time.sleep(5)

Qui si entra nol campo dei trucchi della shell e ci sono metodi migliori per ottenere la stessa cosa.

$ python os_system_background.py
Chiamata...
dom  4 lug 2010, 16.45.32, CEST
In pausa...
dom  4 lug 2010, 16.45.35, CEST

Creare Processi con os.fork()

Le funzioni POSIX fork() ed exec*() (disponibili sotto Mac OS X, Linux, ed altre varianti UNIX) sono esposte tramite il modulo os. Interi libri sono stati scritti circa l'affidabilità dell'uso di queste funzioni, quindi controllare la propria collezione o la libreria per maggiori dettagli rispetto a quelli qui esposti.

Per creare un nuovo processo come clone di quello corrente si usa os.fork().

import os

pid = os.fork()

if pid:
    print 'Id del processo:', pid
else:
    print 'Sono il figlio'

Il proprio output varierà in base allo stato del proprio sistema ogniqualvolta viene eseguito l'esempio, ma dovrebbe assomigliare a questo:

$ python -u os_fork_example.py
Id del processo: 2556
Sono il figlio

Dopo il fork, si ottengono due processi che eseguono lo stesso codice. Per sapere in quale ci si trova, si verifica il valore di ritorno di fork(). Se è 0 ci si trova all'interno del processo figlio. Altrimenti ci si trova nel processo genitore ed il valore restituito + l'identificativo del processo figlio.

Dai processi genitori, è possibile inviare segnali ai figli. La cosa è un poco più complicata da impostare, e si usa il modulo signal. Per prima cosa si definisce un gestore di segnale da chiamare quando viene ricevuto il segnale.

import os
import signal
import time

def signal_usr1(signum, frame):
    "Callback chiamata quando si riceve un segnale"
    pid = os.getpid()
    print 'Ricevuto USR1 nel processo %s' % pid

Quindi si esegue il fork, ed il genitore si mette in pausa per un breve periodo di tempo prima di inviare un segnare USR1 usando os.kill(). La breve pausa consente al processo figlio di avere tempo sufficiente per impostare il gestore di segnale.

    print 'Si esegue il forking...'
    child_pid = os.fork()
    if child_pid:
        print 'GENITORE: In pausa prima di inviare il segnale...'
        time.sleep(1)
        print 'GENITORE: Segnalazione %s' % child_pid
        os.kill(child_pid, signal.SIGUSR1)

Nel figlio, si imposta il gestore di segnale e si mette in pausa per un poco per dare al genitore tempo per inviare al figlio il segnale:

    else:
        print 'FIGLIO: Impostazione del gestore di segnale'
        signal.signal(signal.SIGUSR1, signal_usr1)
        print 'FIGLIO: In pausa in attesa del segnale'
        time.sleep(5)

In una applicazione reale, probabilmente non sarebbe necessario (o non si vorrebbe) chiamare sleep().

$ python os_kill_example.py
Si esegue il forking...
FIGLIO: Impostazione del gestore di segnale
FIGLIO: In pausa in attesa del segnale
Ricevuto USR1 nel processo 13759
Si esegue il forking...
GENITORE: In pausa prima di inviare il segnale...
GENITORE: Segnalazione 13759

Come si vede, un semplice modo di gestire comportamenti separati nel processo figlio è verificare il valore di ritorno di fork() e ramificare. Per un comportamento più complesso, si cerca una maggiore separazione del codice rispetto ad una semplice ramificazione. Negli altri casi, si potrebbe avere un programma esistente da incapsulare. Per entrambe le situazioni, si può usare la serie di funzioni os.exec*() per eseguire un altro programma. Quando si esegue un programma con os.exec*() il codice da quel programma sostituisce il codice del processo esistente.

import os

child_pid = os.fork()
if child_pid:
    os.waitpid(child_pid, 0)
else:
    os.execlp('ls', 'ls', '-l', '/tmp/')
$ python os_exec_example.py
total 64
-rw-------  1 _www       wheel          0 Jun 26 07:31 apr37JbgJ
-rw-------  1 root       wheel       1507 Jun 27 03:00 ccc_exclude.vHRzyU
srwxrwxrwx  1 dhellmann  wheel          0 Jun 26 07:32 com.hp.launchport
drwx------  3 dhellmann  wheel        102 Jun 26 08:54 emacs527
-rw-r--r--  1 dhellmann  wheel      12288 Jun 27 11:40 example.db
drwx------  3 dhellmann  wheel        102 Jun 26 07:32 launch-LwLyiB
drwx------  3 dhellmann  wheel        102 Jun 26 07:32 launch-QoWYy9
drwx------  3 dhellmann  wheel        102 Jun 26 07:32 launch-Y1GkS5
drwx------  3 dhellmann  wheel        102 Jun 26 07:32 launchd-173.E4bnIY
-rw-r--r--  1 dhellmann  wheel      12288 Jun 27 11:22 pymotw_import_example.shelve
drwx------  2 dhellmann  wheel         68 Jun 26 08:02 ssh-2hdhfsgBgC
-rw-r--r--  1 dhellmann  wheel        448 Jun 27 11:23 trace_example.recurse.cover
drwxr-xr-x  2 dhellmann  dhellmann     68 Jun 27 03:15 var_backups

Ci sono molte varianti di exec*(), in dipendenza della forma nella quale si possono avere i parametri, del fatto che si voglia che il percorso e l'ambiente del processo genitore debba essere copiato nel figlio ecc. Dare un'occhiata alla documentazione della libreria per i dettagli completi.

Per tutte le variazioni, il primo parametro è un percorso o nome di file ed i restanti parametri controllano come quel programma venga eseguito. Essi possono essere passati sia tramite parametri di riga comandi o tramite override del processo "environment" (vedere os.environ ed os.getenv).

Aspettare un Figlio

Si supponga che si stiano usando processi multipli per superare i limiti del threading di Python e del Global Interpreter Lock. Se si fanno partire diversi processi per eseguire compiti separati, si vorrà attendere quando uno o più di essi finisca prima di farne partire di nuovi, per evitare il sovraccarico del server. Si sono alcuni modi diversi per fare questo usando wait() e le funzioni collegate.

Se non ci deve preoccupare, o non si conosce, quale processo figlio possa uscire per primo os.wait() ritornerà non appena uno qualsiasi esce:

import os
import sys
import time

for i in range(3):
    print 'GENITORE: Forking %s' % i
    worker_pid = os.fork()
    if not worker_pid:
        print 'WORKER %s: In partenza' % i
        time.sleep(2 + i)
        print 'WORKER %s: Sta finendo' % i
        sys.exit(i)

for i in range(3):
    print 'GENITORE: In attesa di %s' % i
    done = os.wait()
    print 'GENITORE:', done

Si noti che il valore di ritorno da os.wait() è una tuple che contiene l'identificativo del processo e lo stato di uscita (un numero a 16 bit, i cui byte bassi rappresentano il numero di segnale che ha ucciso il processo, e quelli alit rappresentano lo stato di uscita).

$ python os_wait_example.py
GENITORE: Forking 0
GENITORE: Forking 1
WORKER 0: In partenza
WORKER 1: In partenza
GENITORE: Forking 2
GENITORE: In attesa di 0
WORKER 2: In partenza
WORKER 0: Sta finendo
GENITORE: (4135, 0)
GENITORE: In attesa di 1
WORKER 1: Sta finendo
GENITORE: (4136, 256)
GENITORE: In attesa di 2
WORKER 2: Sta finendo
GENITORE: (4137, 512)

Se si vuole attendere uno specifico processo, si usa os.waitpid().

import os
import sys
import time

workers = []
for i in range(3):
    print 'GENITORE: Forking %s' % i
    worker_pid = os.fork()
    if not worker_pid:
        print 'WORKER %s: In partenza' % i
        time.sleep(2 + i)
        print 'WORKER %s: Sta finendo' % i
        sys.exit(i)
    workers.append(worker_pid)

for pid in workers:
    print 'GENITORE: In attesa di  %s' % pid
    done = os.waitpid(pid, 0)
    print 'GENITORE:', done
$ python os_waitpid_example.py
GENITORE: Forking 0
GENITORE: Forking 1
WORKER 0: In partenza
GENITORE: Forking 2
WORKER 1: In partenza
GENITORE: In attesa di  4176
WORKER 2: In partenza
WORKER 0: Sta finendo
GENITORE: (4176, 0)
GENITORE: In attesa di  4177
WORKER 1: Sta finendo
GENITORE: (4177, 256)
GENITORE: In attesa di  4178
WORKER 2: Sta finendo
GENITORE: (4178, 512)

wait3() e wait4() funzionano in modo simile, ma restituiscono informazioni più dettagliate circa il processo figlio con il pid, lo stato di uscita e l'utilizzo della risorsa.

Spawn

Per comodità la famiglia di funzioni spawn*() gestisce le chiamate a fork() ed exec() in una singola istruzione:

import os

os.spawnlp(os.P_WAIT, 'ls', 'ls', '-l', '/tmp/')
$ python os_spawn_example.py
total 64
-rw-------  1 _www       wheel          0 Jun 26 07:31 apr37JbgJ
-rw-------  1 root       wheel       1507 Jun 27 03:00 ccc_exclude.vHRzyU
srwxrwxrwx  1 dhellmann  wheel          0 Jun 26 07:32 com.hp.launchport
drwx------  3 dhellmann  wheel        102 Jun 26 08:54 emacs527
-rw-r--r--  1 dhellmann  wheel      12288 Jun 27 11:40 example.db
drwx------  3 dhellmann  wheel        102 Jun 26 07:32 launch-LwLyiB
drwx------  3 dhellmann  wheel        102 Jun 26 07:32 launch-QoWYy9
drwx------  3 dhellmann  wheel        102 Jun 26 07:32 launch-Y1GkS5
drwx------  3 dhellmann  wheel        102 Jun 26 07:32 launchd-173.E4bnIY
-rw-r--r--  1 dhellmann  wheel      12288 Jun 27 11:22 pymotw_import_example.shelve
drwx------  2 dhellmann  wheel         68 Jun 26 08:02 ssh-2hdhfsgBgC
-rw-r--r--  1 dhellmann  wheel        448 Jun 27 11:23 trace_example.recurse.cover
drwxr-xr-x  2 dhellmann  dhellmann     68 Jun 27 03:15 var_backups

Vedere anche:

os
La documentazione della libreria standard per questo modulo.
subprocess
Il modulo subprocess sostituisce os.popen().
multiprocessing
Il modulo multiprocessing facilita il lavorare con processi extra rispetto al dovere fare tutto il lavoro da soli.
tempfile
Il modulo tempfile per lavorare con file temporanei
Unix Manual Page Introduction
Comprende definizioni per id reali ed effettivi, ecc.
Speaking UNIX, Part 8
Imparare come UNIX esegue il multitask
Unix Concepts
Per saperne di più circa stdin, stdout e stderr.
Delve into Unix Process Creation
Spiega il ciclo di vita di un processo UNIX.
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.