Scopo | Accesso portabile alle funzionalità specifiche di un sistema operativo |
Versione Python | 1.4 (o prececedente) |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per il modulo os
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à.
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.
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:
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']
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:
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
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'
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.
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
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).
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
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
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
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).
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.
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: