os - Accesso Portabile alle Funzionalità Specifiche di un Sistema Operativo

Scopo: Accesso portabile alle funzionalità specifiche di un sistema operativo

Il modulo os fornisce un wrapper 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à.

Esaminare i Contenuti del File System

Per preparare una lista del contenuto di una directory nel file system si usa listdir().

# os_listdir.py

import os
import sys

print(os.listdir(sys.argv[1]))

Il valore ritornato è una lista di tutti i membri nominati della directory passata. Non viene fatta distinzione tra file, sottodirectory o collegamenti simbolici (symlink).

$ python3 os_listdir.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_listdir.py', 'os_listdir.py~',
'os_process_id_example.py', 'os_process_user_example.py',
'os_rename_replace.py', 'os_rename_replace.py~',
'os_scandir.py', 'os_scandir.py~', 'os_spawn_example.py',
'os_stat.py', 'os_stat_chmod.py', 'os_stat_chmod_example.txt',
'os_strerror.py', 'os_strerror.py~', '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']

La funzione walk() attraversa ricorsivamente una directory e per ogni sotto-directory genera una tuple che contiene il percorso della directory, ogni immediata sotto-directory di quel percorso, e una lista di nomi di tutti i file in quella directory.

# os_walk.py

import os
import 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(dir_name)
    # Risalta i nomi delle sotto-directory aggiungendo una /
    sub_dirs = [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('  {}'.format(c))
    print()

Questo esempio mostra una lista ricorsiva di una directory.

$ python3 os_walk.py ../zipimport

../zipimport
  __init__.py
  example_package/
  index.rst
  zipimport_example.zip
  zipimport_find_module.py
  zipimport_get_code.py
  zipimport_get_data.py
  zipimport_get_data_nozip.py
  zipimport_get_data_zip.py
  zipimport_get_source.py
  zipimport_is_package.py
  zipimport_load_module.py
  zipimport_make_example.py

../zipimport/example_package
  README.txt
  __init__.py

Se sono necessarie maggiori informazioni oltre ai nomi dei file, è probabilmente più efficiente usare scandir() in luogo di listdir() visto che molte informazioni vengono raccolte con una sola chiamata di sistema quando la directory viene esaminata.

# os_scandir.py

import os
import sys

for entry in os.scandir(sys.argv[1]):
    if entry.is_dir():
        typ = 'dir'
    elif entry.is_file():
        typ = 'file'
    elif entry.is_symlink():
        typ = 'link'
    else:
        typ = 'sconosciuto'
    print('{name} {typ}'.format(
        name=entry.name,
        typ=typ,
    ))

scandir ritorna una sequenza di istanze di DirEntry per gli elementi nella directory. L'oggetto ha parecchi attributi e metodi per accedere ai metadati riguardo ai file.

$ python3 os_scandir.py .

index.rst file
os_access.py file
os_cwd_example.py file
os_directories.py file
os_environ_example.py file
os_exec_example.py file
os_fork_example.py file
os_kill_example.py file
os_listdir.py file
os_listdir.py~ file
os_process_id_example.py file
os_process_user_example.py file
os_rename_replace.py file
os_rename_replace.py~ file
os_scandir.py file
os_scandir.py~ file
os_spawn_example.py file
os_stat.py file
os_stat_chmod.py file
os_stat_chmod_example.txt file
os_strerror.py file
os_strerror.py~ file
os_symlinks.py file
os_system_background.py file
os_system_example.py file
os_system_shell.py file
os_wait_example.py file
os_waitpid_example.py file
os_walk.py file

Gestire i Permessi del File System

E' possibile accedere a informazioni dettagliate riguardo a un file tramite stat() o lstat (per verificare lo stato di qualcosa che potrebbe essere un collegamento simbolico). .

# os_stat.py

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({}):'.format(filename))
print('  Dimensione     :', stat_info.st_size)
print('  Permessi       :', oct(stat_info.st_mode))
print('  Proprietario   :', stat_info.st_uid)
print('  Dispositivo    :', stat_info.st_dev)
print('  Creato         :', time.ctime(stat_info.st_ctime))
print('  Ultima modifica:', time.ctime(stat_info.st_mtime))
print('  Ultimo accesso :', time.ctime(stat_info.st_atime))

L'output varierà in base a dove viene installato il codice di esempio. Si provi a passare diversi nomi di file nella riga di comando a os_stat.py.

$ python3 os_stat.py  os_stat.py

os.stat(os_stat.py):
  Dimensione     : 570
  Permessi       : 0o100600
  Proprietario   : 1000
  Dispositivo    : 2053
  Creato         : Sat Jan  7 18:38:14 2017
  Ultima modifica: Sat Jan  7 18:38:14 2017
  Ultimo accesso : Sat Jan  7 18:38:14 2017

$ python3 os_stat.py  zipfile_write.zip os.stat(zipfile_write.zip):
  Dimensione     : 197
  Permessi       : 0o100600
  Proprietario   : 1000
  Dispositivo    : 2053
  Creato         : Wed Nov  2 22:43:12 2016
  Ultima modifica: Sun Oct 16 16:45:04 2011
  Ultimo accesso : Sun Nov 13 08:43:44 2016

Nei sitemi tipo Unix, i premessi dei file possono essere cambiati usando chmod(), passando la modalità come intero. I valori delle modalità possono essere costruiti utilizzando le costanti definite nel modulo stat. Questo esempio modifica il bit del permesso di esecuzione per l'utente.

# os_stat_chmod.py

import os
import stat

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

# 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 che abbia i permessi necessari per modificare la modalità del file quando in esecuzione.

$ python3 os_stat_chmod.py

Aggiunta dei permessi di esecuzione

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

# os_access.py
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 varieranno a seconda di dove viene installato il codice di esempio, ma l'output dovrebbe essere simile a questo:

$ python3 os_access.py

Verifica: os_access.py
Esiste: True
Leggibile: True
Scrivibile: True
Eseguibile: False

La documentazione della libreria per access() include due avvertimenti speciali. Primo, non ha molto senso chiamare access() per verificare se un file possa essere aperto prima dell'effettiva chiamata di open() su di esso. Esiste una piccola ma reale finestra temporale tra le due chiamate durante la quale i permessi del file potrebbero cambiare. L'altro avvertimento si applica per lo più a file system di rete che estendono le semantiche dei permessi POSIX. Alcuni tipi di file system potrebbero rispondere alle chiamate POSIX che un processo ha il permesso di accedere a un file, poi riportare un errore quando il tentativo viene fatto usando open() per una qualche ragione non verificato tramite la chiamata POSIX. Tutto sommato, è meglio chiamare open() con la modalità richiesta e catturare l'errore IOError eventualmente sollevato in caso di problemi.

Creare ed Eliminare Directory

Ci sono parecchie funzioni per lavorare con le directory nel file system, incluse quelle per crearle, elencarne il contenuto ed eliminarle.

# os_directories.py

import os

dir_name = 'os_directory_esempio'

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

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

print('Pulizia')
os.unlink(file_name)
os.rmdir(dir_name)

Ci sono due insiemi di funzioni per creare ed eliminare directory. Quando si crea una nuova directory con mkdir(), tutte le directory genitore devono già esistere. Quando si elimina una directory con rmdir(), solo l'ultima directory (l'ultima parte del percorso) viene in realtà rimossa. Di contro, makedirs() e removedirs() operano su tutti i nodi del percorso. makedirs() creerà qualsiasi parte del percorso che non esista e removedirs() eliminerà tutte le directory genitore, fintanto che sono vuote.

$ python3 os_directories.py

Creazione os_directory_esempio
Creazione os_directory_esempio/esempio.txt
Pulizia

Lavorare con i Collegamenti Simbolici

Per le piattaforme e i file system che li supportano, ci sono funzioni che lavorano con i collegamenti simbolici (symlink).

# os_symlinks.py

import os

link_name = '/tmp/' + os.path.basename(__file__)

print('Creaione del collegamento {} -> {}'.format(link_name, __file__))
os.symlink(__file__, link_name)

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

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

# Pulizia
os.unlink(link_name)

Si usa symlink() per creare un collegamento simbolico e readlink() per leggerlo per determinare il file originale puntato dal collegamento. La funzione lstat() funziona come stat(), ma opera sui collegamento simbolico.

$ python3 os_symlinks.py

Creaione del collegamento /tmp/os_symlinks.py -> os_symlinks.py
Permessi: 0o120777
Punta a: os_symlinks.py

Rimpiazzare in Sicurezza un File Esistente

Sostituire o rinominare un file esistante non è idempotente e può esporre le applicazioni a situazioni di race condition. Le fuznioni rename() e replace() implementano algoritmi sicuri per queste azioni, utilizzando operazioni atomiche su sistemi conformi POSIX quando possibile,.

# os_rename_replace.py

import glob
import os


with open('inizio_rinomina.txt', 'w') as f:
    f.write('parte come inizio_rinomina.txt')

print('Partenza:', glob.glob('*rinomina.txt'))

os.rename('inizio_rinomina.txt', 'fine_rinomina.txt')

print('Dopo rinomina:', glob.glob('*rinomina.txt'))

with open('fine_rinomina.txt', 'r') as f:
    print('Contenuti:', repr(f.read()))

with open('rinomina_nuovi_contenuti.txt', 'w') as f:
    f.write('termina con i contenuti di rinomina_nuovi_contenuti.txt')

os.replace('rinomina_nuovi_contenuti.txt', 'fine_rinomina.txt')

with open('fine_rinomina.txt', 'r') as f:
    print('Dopo la sostituzione:', repr(f.read()))

for name in glob.glob('*rinomina.txt'):
    os.unlink(name)

Le funzioni rename() e replace() lavorano attraverso i file system, per la maggior parte delle volte. La rinomina di un file può fallire se esso viene spostato verso un nuovo file system o se la destinazione esiste già.

$ python3 os_rename_replace.py

Partenza: ['inizio_rinomina.txt']
Dopo rinomina: ['fine_rinomina.txt']
Contenuti: 'parte come inizio_rinomina.txt'
Dopo la sostituzione: 'termina con i contenuti di rinomina_nuovi_contenuti.txt'

Identificare e Modificare il Proprietario del Processo

Il prossimo insieme di funzioni fornite da os sono usate per determinare e modificare gli identificativi del proprietario di un processo. Esse sono più frequentemente usate 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., Vedere la lista di riferimenti qui sotto per maggiori dettagli.

L'esempio seguente mostra le vere ed effettive informazioni circa il gruppo e l'utente di un processo, quindi ne cambia i valori effettivi. E' simile a quello che un demone dovrebbe fare quando parte come root durante l'inizializzazione del sistema, per abbassare il livello di privilegi ed essere eseguito come diverso utente.

Prima di eseguire l'esempio modificare i valori di TEST_GID e TEST_UID per farli corrispondere a quelli di un reale utente del proprio sistema.
# os_process_user_example.py

import os

TEST_GID = 502
TEST_UID = 502


def show_user_info():
    print('Utente (reale/effettivo)  : {} / {}'.format(
        os.getuid(), os.geteuid()))
    print('Gruppo (reale/effettivo) : {} / {}'.format(
        os.getgid(), os.getegid()))
    print('Gruppi reali   :', os.getgroups())


print('PRIMA DELLA MDOIFICA:')
show_user_info()
print()

try:
    os.setegid(TEST_GID)
except OSError:
    print('ERRORE: Non è stato possibile cambiare il gruupo effettivo. '
          'Rieseguire come root.')
else:
    print('MODIFICA GRUPPO:')
    show_user_info()
    print()

try:
    os.seteuid(TEST_UID)
except OSError:
    print('ERRORE: Non è stato possibile cambiare l\'utente effettivo. '
          'Rieseguire come root.')
else:
    print('MODIFICA UTENTE:')
    show_user_info()
    print()

Quando eseguito come utente/gruppo 502/502 su sistemi OS X, si vede questo risultato.

$ python3 os_process_user_example.py

PRIMA DELLA MDOIFICA:
User (actual/effective)  : 527 / 527
Group (actual/effective) : 501 / 501
Actual Groups   : [501, 701, 402, 702, 500, 12, 61, 80, 98, 398,
399, 33, 100, 204, 395]

ERRORE: Non è stato possibile cambiare il gruppo effettivo. Rieseguire come root,
ERRORE: Non è stato possibile cambiare l'utente effettivo. Rieseguire come root,

I valori non vengono modificati, visto che non si sta eseguendo come root, un processo non può modificare il valore del proprietario effettivo. Qualsiasi tentativo di impostare l'identificativo utente o il gruppo a qualsiasi cosa diversa dall'utente corrente causa il sollevamento di un errore OSError Eseguendo lo stesso script usando sudo per partire con i privilegi di root è un'altra storia.

$ sudo python3 os_process_user_example.py

PRIMA DELLA MDOIFICA:

User (actual/effective)  : 0 / 0
Group (actual/effective) : 0 / 0
Actual Groups : [0, 1, 2, 3, 4, 5, 8, 9, 12, 20, 29, 61, 80,
702, 33, 98, 100, 204, 395, 398, 399, 701]

MODIFICA GRUPPO:
User (actual/effective)  : 0 / 0
Group (actual/effective) : 0 / 502
Actual Groups   : [0, 1, 2, 3, 4, 5, 8, 9, 12, 20, 29, 61, 80,
702, 33, 98, 100, 204, 395, 398, 399, 701]

MODIFICA UTENTE:
User (actual/effective)  : 0 / 502
Group (actual/effective) : 0 / 502
Actual Groups   : [0, 1, 2, 3, 4, 5, 8, 9, 12, 20, 29, 61, 80,
702, 33, 98, 100, 204, 395, 398, 399, 701]

In questo caso, visto che si è partiti come root, lo script può 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.

Gestire l'Ambiente del Processo

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

# os_environ_example.py
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 modificato:', 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 delle API di Python per recuperare e impostare valori. Le modifiche ad os.environ sono esportate per i processi figli.

$ python3 -u os_environ_example.py

Valore iniziale: None
Processo figlio:


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

Valore rimosso: None
Processo figlio:

Gestire la Directory di Lavoro del Processo

I sistemi operativi con file system gerarchici hanno il concetto di directory di lavoro corrente - la directory nel file system che il processo usa come posizione iniziale quando i file sono indirizzati tramite percorsi relativi. La directory di lavoro corrente può essere recuperata con getpwd() e modificata con chdir().

# os_cwd_example.py
import os

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

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

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

os.curdir ed os.pardir sono usate per riferirsi alle directory attuale e genitore in maniera portabile.

$ python3 os_cwd_example.py

Partenza: .../pymotw-it3.0/dumpscripts
Risalita di un livello: ..
Dopo lo spostamento: .../pymotw-it3.0

Eseguire Comandi Esterni

Molte di queste funzioni per lavorare con i processi hanno una portabilità limitata. Per un modo più consistente di lavorare indipendentemente dalla piattaforma con i processi, si veda il modulo subprocess.

Il modo più basico per eseguire un comando separato, senza interagire con esso, è system(). Riceve un singolo argomento stringa, che è la riga di comando da eseguire da un sotto processo che esegue una shell.

# os_system_example.py
import os

# Semplice comando
os.system('pwd')

Il valore di ritorno di system() è il valore di uscita della shell che sta eseguendo il programma impacchettata in un numero a 16 bit, con il byte più alto con lo stato di uscita e il più basso con il numero di segnale che ha causato l'uscita del processo, oppure zero.

$ python3  -u os_system_example.py

.../pymotw-it3.0/dumpscripts

Visto che il comando è passato direttamente alla shell per l'esecuzione, può includere sintassi di shell tipo globbing oppure variabili di ambiente.

# os_system_shell.py

import os

# Comando con espansione della shell

os.system('echo $TMPDIR')

La variabile di ambiente $TMPDIR in questa stringa viene espansa quando la shell esegue la riga di comando.

$ python3 -u os_system_shell.py

/home/robby/temp

A meno che il comando sia eseguito esplicitamente in background, la chiamata a system() blocca fino a che il comando è completato. I flussi di input, output ed errore dal processo figlio sono legati ai flussi appropriati detenuti dal chiamante in modalità predefinita, ma possono essere rediretti utilizzando la sintassi della shell.

# os_system_background.py

import os
import time

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

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

Si sta andando verso i trucchi della shell tuttavia, e ci sono modi migliori per ottenere la stessa cosa.

$ python3 -u os_system_background.py

Chiamata...
Sat Jan 21 17:03:26 CET 2017
In pausa...
Sat Jan 21 17:03:29 CET 2017

Creare Processi con os.fork()

Le funzioni POSIX fork() ed exec() (a disposizione sotto Mac OS X, Linux e altre varianti Unix) sono esposte tramite il modulo os. Sono stati scritti interi libri circa l'affidabilità nell'utilizzo di queste funzioni, quindi si consultino le pubblicazioni per maggiori dettagli rispetto a quelli presentati in questa introduzione.

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

# os_fork_example.py
import os

pid = os.fork()

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

L'output varierà a seconda dello stato del sistema ogni volta che viene eseguito l'esempio, ma dovrebbe essere circa così:

$ python3 -u os_fork_example.py

Id del processo figlio: 6891
Sono il figlio

Dopo il fork ci sono due processi che stanno eseguendo lo stesso codice. Affinchè un programma possa identificare chi sia chi, deve verificare il valore di ritorno di fork(). Se il valore è 0, il processo corrente è il figlio. Altrimenti il programma è in esecuzione nel processo genitore e il valore di ritorno è l'id del processo figlio.

# os_kill_example.py

import os
import signal
import time


def signal_usr1(signum, frame):
    "Callback chiamto alla ricezione di un segnale"
    pid = os.getpid()
    print('Ricevuto USR1 nel processo {}'.format(pid))

print('Fase di fork...')
child_pid = os.fork()
if child_pid:
    print('GENITORE: In pausa prima di inviare il segnale...')
    time.sleep(1)
    print('GENITORE: In segnalazione {}'.format(child_pid))
    os.kill(child_pid, signal.SIGUSR1)
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)

Il genitore può inviare segnali al processo figlio usando kill() e il modulo signal. Per prima cosa definisce un gestore di segnale da chiamare quando il segnale viene ricevuto. Poi esegue il fork() e nel genitore si mette in pausa per un breve periodo prima di inviare un segnale USR1 utilizzando kill(). Questo esempio usa una breve pausa per dare tempo al processo figlio di impostare il gestore di segnale. Una vera applicazione, non dovrebbe (o vorrebbe) chiamare sleep(). Nel figlio si imposta il gestore di segnale quindi si mette in pausa per un poco per dare tempo al genitore di inviare il segnale.

$ python3 -u os_kill_example.py

Fase di fork...
GENITORE: In pausa prima di inviare il segnale...
FIGLIO: Impostazione del gestore di segnale
FIGLIO: In pausa, in attesa del segnale
GENITORE: Segnalazione di 7461
Ricevuto USR1 nel processo 7461

Un semplice modo per gestire un comportamento separato nel processo figlio è quello di verificare il valore di ritorno di fork() e diramare. Un comportamento maggiormente complesso potrebbe riguardare una separazione del codice maggiore che una semplice diramazione. In altri casi, potrebbe esserci un programma esistente che deve essere impacchettato. Per entrambe queste situazioni le serie di funzioni che iniziano con exec*() possono essere usate per eseguire un altro programma.

# os_exec_example.py
import os

child_pid = os.fork()
if child_pid:
    os.waitpid(child_pid, 0)
else:
    os.execlp('pwd', 'pwd', '-P')

Quando un programma viene eseguito da exec() il codice da quel programma sostituisce il codice dal processo esistente.

$ python3 -u os_exec_example.py

.../python/pymotw-it3.0/dumpscripts

Ci sono molte varianti di exec(), a seconda della forma nella quale siano disponibili gli argomenti, oppure in base al fatto che il percorso e l'ambiente del processo genitore devve essere copiato nel figlio ecc. Per tutte le varianti il primo argomento è il percorso o il nome del file e i restanti argomenti controllano il modo in cui il programma viene eseguito. Essi sono passati sia come argomenti di riga di comando oppure sovrascrivendo l'ambiente del processo (si veda os.environ e os.getenv()). Fare riferimento alla documentazione della libreria per maggiori dettagli.

In Attesa dei Processi Figli

Diversi programmi molto esigenti dal punto di vista del calcolo utilizzano processori multipli per aggirare le limitazioni nella gestione dei thread e il lock globale dell'interprete. Quando si fanno partire parecchi processi per eseguire compiti separati, il processo principale dovrà attendere che finiscano uno o più di questi prima di farne partire di nuovi, per evitare un sovraccarico del server. Ci sono alcuni modi diversi per fare questo utilizzando wait() e le funzioni correlate.

Quando non ha importanza quale processo figlio debba uscire per primo, si usa wait(). Ritorna non appena un qualsiasi processo figlio esce.

# os_wait_example.py

import os
import sys
import time

for i in range(2):
    print('GENITORE {}: Forking {}'.format(os.getpid(), i))
    worker_pid = os.fork()
    if not worker_pid:
        print('WORKER {}: In partenza'.format(i))
        time.sleep(2 + i)
        print('WORKER {}: Sta finendo'.format(i))
        sys.exit(i)

for i in range(2):
    print('GENITORE: In attesa di {}'.format(i))
    done = os.wait()
    print('GENITORE: Figlio completato:', done)

Il valore di ritorno da wait() è una tuple che contiene l'identificativo del processo e lo stato di uscita combinati in un valore a 16 bit. Il bite inferiore è il numero del segnale che ha ucciso il processo mentre quello superiore è il codice di stato ritornato dal processo in uscita.

$ python3 os_wait_example.py

ENITORE 7008: Forking 0
GENITORE 7008: Forking 1
WORKER 0: In partenza
GENITORE: In attesa di 0
WORKER 1: In partenza
WORKER 0: Sta finendo
GENITORE: Figlio completato: (7009, 0)
GENITORE: In attesa di 1
WORKER 1: Sta finendo
GENITORE: Figlio completato: (7010, 256)

Per attendere l'uscita di un processo specifico si usa waitpid().

# os_waitpid_example.py

import os
import sys
import time

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

for pid in workers:
    print('GENITORE: In attesa di  {}'.format(pid))
    done = os.waitpid(pid, 0)
    print('GENITORE: Figlio completato:', done)

Si passa l'identificativo del processo scelto e waitpid() si interrompe fino a che quel processo esce.

$ python3 os_waitpid_example.py

GENITORE 7656: Forking 0
GENITORE 7656: Forking 1
WORKER 0: In partenza
GENITORE: In attesa di  7657
WORKER 1: In partenza
WORKER 0: Sta finendo
GENITORE: Figlio completato: (7657, 0)
GENITORE: In attesa di  7658
WORKER 1: Sta finendo
GENITORE: Figlio completato: (7658, 256)

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

Produrre Nuovi Processi

Per comodità, la famiglia di funzioni spawn() gestisce fork() ed exec() in una sola istruzione:

# os_spawn_example.py
import os

os.spawnlp(os.P_WAIT, 'pwd', 'pwd', '-P')

Il primo argomento è una modalità che indica se attendere o meno la fine del processo prima di ritornare. Questo esempio attende. Si usa P_NOWAIT per consentire agli altri processi di partire, poi riprendere nel processo corrente.

$ python3 os_spawn_example.py

.../pymotw-it3.0/dumpscripts

Codici di Errore del Sistema Operativo

I codici di errore definiti dai sistemi operativi e gestiti dal modulo errno possono essere trasformati in stringhe di messaggi usando strerror().

# os_strerror.py

import errno
import os

for num in [errno.ENOENT, errno.EINTR, errno.EBUSY]:
    name = errno.errorcode[num]
    print('[{num:>2}] {name:<6}: {msg}'.format(
        name=name, num=num, msg=os.strerror(num)))

Questo esempio mostra i messaggi associati ad alcuni codici di errore che capitano frequentemente.

$ python3 os_strerror.py

[ 2] ENOENT: No such file or directory
[ 4] EINTR : Interrupted system call
[16] EBUSY : Device or resource busy

Vedere anche:

os
La documentazione della libreria standard per questo modulo.
Note di portabilità per os
signal
Dettaglio delle tecniche di gestione del segnale (in corso di traduzione)
subprocess
Il modulo subprocess sostituisce os.popen() (in corso di traduzione)
multiprocessing
Il modulo multiprocessing facilita il lavoro con ulteriori processi (in corso di traduzione)
tempfile
Il modulo tempfile per lavorare con file temporanei
Lavorare con l'alberatura delle directory
Il modulo shutil include anche funzioni per lavorare con l'alberatura delle directory
Speaking UNIX, Part 8
Imparare come UNIX esegue il multitask
Canali Standard
Per ulteriori discussioni su 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.