os — Acceso portable a funciones específicas del sistema operativo

Propósito:Acceso portable a funciones específicas del sistema operativo.

El módulo os proporciona un contenedor para módulos específicos de la plataforma, tales como posix, nt y mac. La interfaz de programación para las funciones disponibles en todas las plataformas debe ser la misma, por lo que usar el módulo os ofrece cierta medida de portabilidad. Sin embargo, no todas las funciones están disponibles en todas las plataformas. Muchas de las funciones de gestión de procesos descritas en este resumen no están disponibles para Windows.

La documentación de Python para el módulo os se subtitula «Interfaces diversas del sistema operativo». El módulo consta principalmente de funciones para crear y administrar procesos en ejecución o contenido del sistema de archivos (archivos y directorios), además de algunos otras funcionalidades.

Examinar el contenido del sistema de archivos

Para preparar una lista de los contenidos de un directorio en el sistema de archivos, usa listdir().

os_listdir.py
import os
import sys

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

El valor de retorno es una lista sin clasificar de todos los miembros nombrados del directorio dado. No se hace distinción entre archivos, subdirectorios o enlaces simbólicos.

$ 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 función walk() atraviesa un directorio de forma recursiva y para cada subdirectorio genera un tuple que contiene la ruta del directorio, cualquier subdirectorio inmediato de esa ruta y una lista de los nombres de los archivos en ese directorio.

os_walk.py
import os
import sys

# If we are not given a path to list, use /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)
    # Make the subdirectory names stand out with /
    sub_dirs = [n + '/' for n in sub_dirs]
    # Mix the directory contents together
    contents = sub_dirs + files
    contents.sort()
    # Show the contents
    for c in contents:
        print('  {}'.format(c))
    print()

Este ejemplo muestra un listado de directorio recursivo.

$ 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

Si se necesita más información que los nombres de los archivos, es probable que sea más eficiente usar scandir() que listdir() porque se recopila más información en una llamada al sistema cuando se escanea el directorio .

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 = 'unknown'
    print('{name} {typ}'.format(
        name=entry.name,
        typ=typ,
    ))

scandir() devuelve una secuencia de instancias DirEntry para los elementos en el directorio. El objeto tiene varios atributos y métodos para acceder a metadatos sobre el archivo.

$ 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

Administrar permisos del sistema de archivos

Se puede acceder a información detallada sobre un archivo usando stat() o lstat() (para verificar el estado de algo que podría ser un enlace simbólico).

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('  Size:', stat_info.st_size)
print('  Permissions:', oct(stat_info.st_mode))
print('  Owner:', stat_info.st_uid)
print('  Device:', stat_info.st_dev)
print('  Created      :', time.ctime(stat_info.st_ctime))
print('  Last modified:', time.ctime(stat_info.st_mtime))
print('  Last accessed:', time.ctime(stat_info.st_atime))

El resultado variará dependiendo de cómo se instaló el código de ejemplo. Intenta pasar diferentes nombres de archivo en la línea de comando a os_stat.py.

$ python3 os_stat.py

os.stat(os_stat.py):
  Size: 593
  Permissions: 0o100644
  Owner: 527
  Device: 16777218
  Created      : Sat Dec 17 12:09:51 2016
  Last modified: Sat Dec 17 12:09:51 2016
  Last accessed: Sat Dec 31 12:33:19 2016

$ python3 os_stat.py index.rst

os.stat(index.rst):
  Size: 26878
  Permissions: 0o100644
  Owner: 527
  Device: 16777218
  Created      : Sat Dec 31 12:33:10 2016
  Last modified: Sat Dec 31 12:33:10 2016
  Last accessed: Sat Dec 31 12:33:19 2016

En sistemas tipo Unix, los permisos de archivo se pueden cambiar usando chmod(), pasando el modo como un entero. Los valores de modo pueden construirse usando constantes definidas en el módulo stat. Este ejemplo alterna el bit de permiso de ejecución del usuario:

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('contents')

# Determine what permissions are already set using stat
existing_permissions = stat.S_IMODE(os.stat(filename).st_mode)

if not os.access(filename, os.X_OK):
    print('Adding execute permission')
    new_permissions = existing_permissions | stat.S_IXUSR
else:
    print('Removing execute permission')
    # use xor to remove the user execute permission
    new_permissions = existing_permissions ^ stat.S_IXUSR

os.chmod(filename, new_permissions)

La secuencia de comandos asume que tiene los permisos necesarios para modificar el modo del archivo cuando se ejecuta.

$ python3 os_stat_chmod.py

Adding execute permission

La función access() se puede usar para probar los derechos de acceso que un proceso tiene para un archivo.

os_access.py
import os

print('Testing:', __file__)
print('Exists:', os.access(__file__, os.F_OK))
print('Readable:', os.access(__file__, os.R_OK))
print('Writable:', os.access(__file__, os.W_OK))
print('Executable:', os.access(__file__, os.X_OK))

Los resultados variarán según cómo esté instalado el código de ejemplo, pero el resultado será similar a esto:

$ python3 os_access.py

Testing: os_access.py
Exists: True
Readable: True
Writable: True
Executable: False

La documentación de la biblioteca para access() incluye dos advertencias especiales. Primero, no tiene mucho sentido llamar a access() para probar si se puede abrir un archivo antes de llamar a open() en él. Hay una pequeña, pero real, ventana de tiempo entre las dos llamadas durante la cual los permisos en el archivo podrían cambiar. La otra advertencia se aplica principalmente a los sistemas de archivos en red que extienden la semántica de permisos POSIX. Algunos tipos de sistemas de archivos pueden responder a la llamada POSIX de que un proceso tiene permiso para acceder a un archivo, luego informan una falla cuando el intento se realiza usando open() por alguna razón no probada a través de la llamada POSIX. Con todo, es mejor llamar a open() con el modo requerido y detectar el IOError generado si hay un problema.

Crear y eliminar directorios

Existen varias funciones para trabajar con directorios en el sistema de archivos, incluida la creación, el listado de contenidos y su eliminación.

os_directories.py
import os

dir_name = 'os_directories_example'

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

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

print('Cleaning up')
os.unlink(file_name)
os.rmdir(dir_name)

Hay dos conjuntos de funciones para crear y eliminar directorios. Al crear un nuevo directorio con mkdir(), todos los directorios principales ya deben existir. Al eliminar un directorio con rmdir(), solo se elimina realmente el directorio hoja (la última parte de la ruta). En contraste, makedirs() y removeirs() operan en todos los nodos en la ruta. makedirs() creará cualquier parte de la ruta que no exista, y removedirs() eliminará todos los directorios principales, siempre que estén vacíos.

$ python3 os_directories.py

Creating os_directories_example
Creating os_directories_example/example.txt
Cleaning up

Trabajar con enlaces simbólicos

Para las plataformas y los sistemas de archivos que los admiten, existen funciones para trabajar con enlaces simbólicos.

os_symlinks.py
import os

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

print('Creating link {} -> {}'.format(link_name, __file__))
os.symlink(__file__, link_name)

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

print('Points to:', os.readlink(link_name))

# Cleanup
os.unlink(link_name)

Use symlink() para crear un enlace simbólico y readlink() para leerlo y determinar el archivo original al que apunta el enlace. La función lstat() es como stat(), pero opera en enlaces simbólicos.

$ python3 os_symlinks.py

Creating link /tmp/os_symlinks.py -> os_symlinks.py
Permissions: 0o120755
Points to: os_symlinks.py

Sustitución segura de un archivo existente

Reemplazar o renombrar un archivo existente no es idempotente y puede exponer las aplicaciones a condiciones de carrera. Las funciones rename() y replace() implementan algoritmos seguros para estas acciones, utilizando operaciones atómicas en sistemas compatibles con POSIX cuando sea posible.

os_rename_replace.py
import glob
import os


with open('rename_start.txt', 'w') as f:
    f.write('starting as rename_start.txt')

print('Starting:', glob.glob('rename*.txt'))

os.rename('rename_start.txt', 'rename_finish.txt')

print('After rename:', glob.glob('rename*.txt'))

with open('rename_finish.txt', 'r') as f:
    print('Contents:', repr(f.read()))

with open('rename_new_contents.txt', 'w') as f:
    f.write('ending with contents of rename_new_contents.txt')

os.replace('rename_new_contents.txt', 'rename_finish.txt')

with open('rename_finish.txt', 'r') as f:
    print('After replace:', repr(f.read()))

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

Las funciones rename() y replace() funcionan en sistemas de archivos, la mayoría de las veces. El cambio de nombre de un archivo puede fallar si se está moviendo a un nuevo sistema de archivos o si el destino ya existe.

$ python3 os_rename_replace.py

Starting: ['rename_start.txt']
After rename: ['rename_finish.txt']
Contents: 'starting as rename_start.txt'
After replace: 'ending with contents of rename_new_contents.txt'

Determinar y cambiar el propietario del proceso

El siguiente conjunto de funciones proporcionadas por os se utiliza para determinar y cambiar los identificadores de propietario del proceso. Estos son utilizados con mayor frecuencia por los autores de demonios o programas especiales del sistema que necesitan cambiar el nivel de permiso en lugar de ejecutarse como root. Esta sección no trata de explicar todos los detalles intrincados de la seguridad de Unix, los propietarios de procesos, etc. Consulta la lista de referencias al final de esta sección para obtener más detalles.

El siguiente ejemplo muestra la información real y efectiva del usuario y del grupo para un proceso, y luego cambia los valores efectivos. Esto es similar a lo que un demonio necesitaría hacer cuando se inicia como root durante un arranque del sistema, para reducir el nivel de privilegio y ejecutarse como un usuario diferente.

Nota

Antes de ejecutar el ejemplo, cambia los valores TEST_GID y TEST_UID para que coincidan con un usuario real definido en el sistema.

os_process_user_example.py
import os

TEST_GID = 502
TEST_UID = 502


def show_user_info():
    print('User (actual/effective)  : {} / {}'.format(
        os.getuid(), os.geteuid()))
    print('Group (actual/effective) : {} / {}'.format(
        os.getgid(), os.getegid()))
    print('Actual Groups   :', os.getgroups())


print('BEFORE CHANGE:')
show_user_info()
print()

try:
    os.setegid(TEST_GID)
except OSError:
    print('ERROR: Could not change effective group. '
          'Rerun as root.')
else:
    print('CHANGE GROUP:')
    show_user_info()
    print()

try:
    os.seteuid(TEST_UID)
except OSError:
    print('ERROR: Could not change effective user. '
          'Rerun as root.')
else:
    print('CHANGE USER:')
    show_user_info()
    print()

Cuando se ejecuta como usuario con el ID 502 y el grupo 502 en OS X, se produce esta salida:

$ python3 os_process_user_example.py

BEFORE CHANGE:
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]

ERROR: Could not change effective group. Rerun as root.
ERROR: Could not change effective user. Rerun as root.

Los valores no cambian porque cuando no se ejecuta como raíz, un proceso no puede cambiar su valor de propietario efectivo. Cualquier intento de establecer la identificación de usuario efectiva o la identificación de grupo en otra cosa que no sea la del usuario actual provoca un OSError. Ejecutar la misma secuencia de comandos usando sudo para que comience con privilegios de root es una historia diferente.

$ sudo python3 os_process_user_example.py

BEFORE CHANGE:

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]

CHANGE GROUP:
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]

CHANGE USER:
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]

En este caso, dado que comienza como root, la secuencia de comandos puede cambiar el usuario efectivo y el grupo para el proceso. Una vez que se cambia el UID efectivo, el proceso se limita a los permisos de ese usuario. Debido a que los usuarios no root no pueden cambiar su grupo efectivo, el programa necesita cambiar el grupo antes de cambiar el usuario.

Gestionar el entorno del proceso

Otra característica del sistema operativo expuesta a un programa a través del módulo os es el entorno. Las variables establecidas en el entorno son visibles como cadenas que se pueden leer a través de os.environ o getenv(). Las variables de entorno se usan comúnmente para valores de configuración, como rutas de búsqueda, ubicaciones de archivos e indicadores de depuración. Este ejemplo muestra cómo recuperar una variable de entorno y pasar un valor a un proceso secundario.

os_environ_example.py
import os

print('Initial value:', os.environ.get('TESTVAR', None))
print('Child process:')
os.system('echo $TESTVAR')

os.environ['TESTVAR'] = 'THIS VALUE WAS CHANGED'

print()
print('Changed value:', os.environ['TESTVAR'])
print('Child process:')
os.system('echo $TESTVAR')

del os.environ['TESTVAR']

print()
print('Removed value:', os.environ.get('TESTVAR', None))
print('Child process:')
os.system('echo $TESTVAR')

El objeto os.environ sigue la interfaz de programación estándar de mapeo de Python para recuperar y establecer valores. Los cambios en os.environ se exportan para procesos secundarios.

$ python3 -u os_environ_example.py

Initial value: None
Child process:

Changed value: THIS VALUE WAS CHANGED
Child process:
THIS VALUE WAS CHANGED

Removed value: None
Child process:

Administrar el directorio de trabajo del proceso

Los sistemas operativos con sistemas de archivos jerárquicos tienen un concepto del directorio de trabajo actual: el directorio en el sistema de archivos que el proceso usa como ubicación de inicio cuando se accede a los archivos con rutas relativas. El directorio de trabajo actual se puede recuperar con getcwd() y cambiar con chdir().

os_cwd_example.py
import os

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

print('Moving up one:', os.pardir)
os.chdir(os.pardir)

print('After move:', os.getcwd())

os.curdir y os.pardir se utilizan para referirse a los directorios actuales y principales de manera portable.

$ python3 os_cwd_example.py

Starting: .../pymotw-3/source/os
Moving up one: ..
After move: .../pymotw-3/source

Ejecutar comandos externos

Advertencia

Muchas de estas funciones para trabajar con procesos tienen una portabilidad limitada. Para una forma más consistente de trabajar con procesos de manera independiente de la plataforma, ve el módulo :mod: subprocess en su lugar.

La forma más básica de ejecutar un comando por separado, sin interactuar con él, es system(). Toma un solo argumento de cadena, que es la línea de comando que debe ejecutar un subproceso que ejecuta un shell.

os_system_example.py
import os

# Simple command
os.system('pwd')

El valor de retorno de system() es el valor de salida del shell que ejecuta el programa empaquetado en un número de 16 bits, con el byte alto el estado de salida y el byte bajo el número de señal que causó la muerte del proceso, o cero.

$ python3 -u os_system_example.py

.../pymotw-3/source/os

Dado que el comando se pasa directamente al shell para su procesamiento, puede incluir la sintaxis del shell, como el globbing o las variables de entorno.

os_system_shell.py
import os

# Command with shell expansion
os.system('echo $TMPDIR')

La variable de entorno $TMPDIR en esta cadena se expande cuando el shell ejecuta la línea de comando.

$ python3 -u os_system_shell.py

/var/folders/5q/8gk0wq888xlggz008k8dr7180000hg/T/

A menos que el comando se ejecute explícitamente en segundo plano, la llamada a system() se bloquea hasta que se complete. La entrada, salida y error estándar del proceso secundario están vinculados a las secuencias apropiadas que posee la persona que llama de forma predeterminada, pero se pueden redirigir utilizando la sintaxis de shell.

os_system_background.py
import os
import time

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

print('Sleeping...')
time.sleep(5)

Sin embargo, esto está entrando en trucos del shell, y hay mejores formas de lograr lo mismo.

$ python3 -u os_system_background.py

Calling...
Sat Dec 31 12:33:20 EST 2016
Sleeping...
Sat Dec 31 12:33:23 EST 2016

Crear procesos con os.fork()

Las funciones POSIX fork() y exec() (disponibles en Mac OS X, Linux y otras variantes de Unix) se exponen a través del módulo os. Se han escrito libros completos sobre el uso confiable de estas funciones, por lo tanto, consulta la biblioteca o la librería para obtener más detalles de los que se presentan aquí en esta introducción.

Para crear un nuevo proceso como un clon del proceso actual, usa fork():

os_fork_example.py
import os

pid = os.fork()

if pid:
    print('Child process id:', pid)
else:
    print('I am the child')

El resultado variará según el estado del sistema cada vez que se ejecute el ejemplo, pero se verá más o menos así:

$ python3 -u os_fork_example.py

Child process id: 29190
I am the child

Después del fork, hay dos procesos que ejecutan el mismo código. Para que un programa indique en cuál está, debe verificar el valor de retorno de fork().

Si el valor es 0, el proceso actual es el hijo. Si no es 0, el programa se está ejecutando en el proceso primario y el valor de retorno es la identificación del proceso secundario.

os_kill_example.py
import os
import signal
import time


def signal_usr1(signum, frame):
    "Callback invoked when a signal is received"
    pid = os.getpid()
    print('Received USR1 in process {}'.format(pid))


print('Forking...')
child_pid = os.fork()
if child_pid:
    print('PARENT: Pausing before sending signal...')
    time.sleep(1)
    print('PARENT: Signaling {}'.format(child_pid))
    os.kill(child_pid, signal.SIGUSR1)
else:
    print('CHILD: Setting up signal handler')
    signal.signal(signal.SIGUSR1, signal_usr1)
    print('CHILD: Pausing to wait for signal')
    time.sleep(5)

El padre puede enviar señales al proceso hijo usando kill() y el módulo signal. Primero, define un manejador de señal que se invocará cuando se reciba la señal. Luego fork(), y en el padre pausa un corto período de tiempo antes de enviar una señal USR1 usando kill(). Este ejemplo utiliza una breve pausa para darle tiempo al proceso secundario para configurar el controlador de señal. Una aplicación real, no necesitaría (o querría) llamar a sleep(). En el proceso hijo, configura el controlador de señal y vaya a dormir por un tiempo para darle tiempo a los padres para enviar la señal.

$ python3 -u os_kill_example.py

Forking...
PARENT: Pausing before sending signal...
CHILD: Setting up signal handler
CHILD: Pausing to wait for signal
PARENT: Signaling 29193
Received USR1 in process 29193

Una forma simple de manejar un comportamiento separado en el proceso secundario es verificar el valor de retorno de fork() y salir. Un comportamiento más complejo puede requerir más separación de código que una salida simple. En otros casos, puede haber un programa existente que necesita ser ajustado. Para ambas situaciones, la serie de funciones exec*() se puede usar para ejecutar otro programa.

os_exec_example.py
import os

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

Cuando un programa es ejecutado por exec(), el código de ese programa reemplaza el código del proceso existente.

$ python3 os_exec_example.py

.../pymotw-3/source/os

Existen muchas variaciones de exec(), dependiendo de la forma en que estén disponibles los argumentos, si la ruta y el entorno del proceso padre deben copiarse al hijo, etc. Para todas las variaciones, el primer argumento es una ruta o nombre de archivo y los argumentos restantes controlan cómo se ejecuta ese programa. Se pasan como argumentos de línea de comandos o anulan el «entorno» del proceso (ve os.environ y os.getenv). Consulta la documentación de la biblioteca para obtener detalles completos.

Esperar a procesos secundarios

Muchos programas computacionalmente intensivos utilizan múltiples procesos para evitar las limitaciones de subprocesos de Python y el bloqueo global del intérprete. Al iniciar varios procesos para ejecutar tareas separadas, el proceso maestro tendrá que esperar a que uno o más de ellos finalicen antes de iniciar otros nuevos, para evitar sobrecargar el servidor. Hay algunas formas diferentes de hacerlo usando wait() y funciones relacionadas.

Cuando no importa qué proceso secundario podría finalizar primero, usa wait(). Regresa tan pronto como se cierra cualquier proceso hijo.

os_wait_example.py
import os
import sys
import time

for i in range(2):
    print('PARENT {}: Forking {}'.format(os.getpid(), i))
    worker_pid = os.fork()
    if not worker_pid:
        print('WORKER {}: Starting'.format(i))
        time.sleep(2 + i)
        print('WORKER {}: Finishing'.format(i))
        sys.exit(i)

for i in range(2):
    print('PARENT: Waiting for {}'.format(i))
    done = os.wait()
    print('PARENT: Child done:', done)

El valor de retorno de wait() es una tupla que contiene la identificación del proceso y el estado de salida combinados en un valor de 16 bits. El byte bajo es el número de la señal que mató el proceso, y el byte alto es el código de estado devuelto por el proceso cuando salió.

$ python3 -u os_wait_example.py

PARENT 29202: Forking 0
PARENT 29202: Forking 1
PARENT: Waiting for 0
WORKER 0: Starting
WORKER 1: Starting
WORKER 0: Finishing
PARENT: Child done: (29203, 0)
PARENT: Waiting for 1
WORKER 1: Finishing
PARENT: Child done: (29204, 256)

Para esperar un proceso específico, usa waitpid().

os_waitpid_example.py
import os
import sys
import time

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

for pid in workers:
    print('PARENT: Waiting for {}'.format(pid))
    done = os.waitpid(pid, 0)
    print('PARENT: Child done:', done)

Pasa la identificación del proceso del proceso de destino y waitpid() bloquea hasta que ese proceso finalice.

$ python3 -u os_waitpid_example.py

PARENT 29211: Forking 0
PARENT 29211: Forking 1
PARENT: Waiting for 29212
WORKER 0: Starting
WORKER 1: Starting
WORKER 0: Finishing
PARENT: Child done: (29212, 0)
PARENT: Waiting for 29213
WORKER 1: Finishing
PARENT: Child done: (29213, 256)

wait3() y wait4() funcionan de manera similar, pero devuelven información más detallada sobre el proceso secundario con el pid, el estado de salida y el uso de recursos.

Generar nuevos procesos

Como conveniencia, la familia de funciones spawn() maneja el fork() y exec() en una declaración:

os_spawn_example.py
import os

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

El primer argumento es un modo que indica si esperar o no a que finalice el proceso antes de regresar. Este ejemplo espera. Usa P_NOWAIT para permitir que comience el otro proceso, pero luego continúe en el proceso actual.

$ python3 os_spawn_example.py

.../pymotw-3/source/os

Códigos de error del sistema operativo

Los códigos de error definidos por el sistema operativo y administrados en el módulo errno se pueden traducir a cadenas de mensajes 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)))

Este ejemplo muestra los mensajes asociados con algunos códigos de error que aparecen con frecuencia.

$ python3 os_strerror.py

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

Ver también