os – Portable access to operating system specific features.¶
Purpose: | Portable access to operating system specific features. |
---|---|
Available In: | 1.4 (or earlier) |
The os module provides a wrapper for platform specific modules such as posix, nt, and mac. The API for functions available on all platform should be the same, so using the os module offers some measure of portability. Not all functions are available on all platforms, however. Many of the process management functions described in this summary are not available for Windows.
The Python documentation for the os module is subtitled “Miscellaneous operating system interfaces”. The module consists mostly of functions for creating and managing running processes or filesystem content (files and directories), with a few other bits of functionality thrown in besides.
Note
Some of the example code below will only work on Unix-like operating systems.
Process Owner¶
The first set of functions to cover are used for determining and changing the process owner ids. These are mostly useful to authors of daemons or special system programs which need to change permission level rather than running as root. This section does not try to explain all of the intricate details of Unix security, process owners, etc. See the References list below for more details.
This first script shows the real and effective user and group information for a process, and then changes the effective values. This is similar to what a daemon would need to do when it starts as root during a system boot, to lower the privilege level and run as a different user.
Note
Before running the example, change the TEST_GID and TEST_UID values to match a real user.
import os
TEST_GID=501
TEST_UID=527
def show_user_info():
print 'Effective User :', os.geteuid()
print 'Effective Group :', os.getegid()
print 'Actual User :', os.getuid(), os.getlogin()
print 'Actual Group :', os.getgid()
print 'Actual Groups :', os.getgroups()
return
print 'BEFORE CHANGE:'
show_user_info()
print
try:
os.setegid(TEST_GID)
except OSError:
print 'ERROR: Could not change effective group. Re-run as root.'
else:
print 'CHANGED GROUP:'
show_user_info()
print
try:
os.seteuid(TEST_UID)
except OSError:
print 'ERROR: Could not change effective user. Re-run as root.'
else:
print 'CHANGE USER:'
show_user_info()
print
When run as user with id of 527 and group 501 on OS X, this output is produced:
$ python os_process_user_example.py
BEFORE CHANGE:
Effective User : 527
Effective Group : 501
Actual User : 527 dhellmann
Actual Group : 501
Actual Groups : [501, 401, 101, 500, 12, 33, 61, 80, 98, 100, 204, 102]
CHANGED GROUP:
Effective User : 527
Effective Group : 501
Actual User : 527 dhellmann
Actual Group : 501
Actual Groups : [501, 401, 101, 500, 12, 33, 61, 80, 98, 100, 204, 102]
CHANGE USER:
Effective User : 527
Effective Group : 501
Actual User : 527 dhellmann
Actual Group : 501
Actual Groups : [501, 401, 101, 500, 12, 33, 61, 80, 98, 100, 204, 102]
Notice that the values do not change. When not running as root, processes cannot change their effective owner values. Any attempt to set the effective user id or group id to anything other than that of the current user causes an OSError.
Running the same script using sudo so that it starts out with root privileges is a different story.
$ sudo python os_process_user_example.py
BEFORE CHANGE:
Effective User : 0
Effective Group : 0
Actual User : 0 dhellmann
Actual Group : 0
Actual Groups : [0, 1, 2, 8, 29, 3, 9, 4, 5, 80, 20]
CHANGED GROUP:
Effective User : 0
Effective Group : 501
Actual User : 0 dhellmann
Actual Group : 0
Actual Groups : [501, 1, 2, 8, 29, 3, 9, 4, 5, 80, 20]
CHANGE USER:
Effective User : 527
Effective Group : 501
Actual User : 0 dhellmann
Actual Group : 0
Actual Groups : [501, 1, 2, 8, 29, 3, 9, 4, 5, 80, 20]
In this case, since it starts as root, it can change the effective user and group for the process. Once the effective UID is changed, the process is limited to the permissions of that user. Since non-root users cannot change their effective group, the program needs to change the group before changing the user.
Besides finding and changing the process owner, there are functions for determining the current and parent process id, finding and changing the process group and session ids, as well as finding the controlling terminal id. These can be useful for sending signals between processes or for complex applications such as writing a command line shell.
Process Environment¶
Another feature of the operating system exposed to a program though the os module is the environment. Variables set in the environment are visible as strings that can be read through os.environ or getenv(). Environment variables are commonly used for configuration values such as search paths, file locations, and debug flags. This example shows how to retrieve an environment variable, and pass a value through to a child process.
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')
The os.environ object follows the standard Python mapping API for retrieving and setting values. Changes to os.environ are exported for child processes.
$ python -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:
Process Working Directory¶
Operating systems with hierarchical filesystems have a concept of the current working directory – the directory on the filesystem the process uses as the starting location when files are accessed with relative paths. The current working directory can be retrieved with getcwd() and changed with chdir().
import os
print 'Starting:', os.getcwd()
print 'Moving up one:', os.pardir
os.chdir(os.pardir)
print 'After move:', os.getcwd()
os.curdir and os.pardir are used to refer to the current and parent directories in a portable manner. The output should not be surprising:
$ python os_cwd_example.py
Starting: /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/os
Moving up one: ..
After move: /Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW
Pipes¶
The os module provides several functions for managing the I/O of child processes using pipes. The functions all work essentially the same way, but return different file handles depending on the type of input or output desired. For the most part, these functions are made obsolete by the subprocess module (added in Python 2.4), but there is a good chance legacy code uses them.
The most commonly used pipe function is popen(). It creates a new process running the command given and attaches a single stream to the input or output of that process, depending on the mode argument. While popen() functions work on Windows, some of these examples assume a Unix-like shell.
import os
print 'popen, read:'
pipe_stdout = os.popen('echo "to stdout"', 'r')
try:
stdout_value = pipe_stdout.read()
finally:
pipe_stdout.close()
print '\tstdout:', repr(stdout_value)
print '\npopen, write:'
pipe_stdin = os.popen('cat -', 'w')
try:
pipe_stdin.write('\tstdin: to stdin\n')
finally:
pipe_stdin.close()
The descriptions of the streams also assume Unix-like terminology:
- stdin - The “standard input” stream for a process (file descriptor 0) is readable by the process. This is usually where terminal input goes.
- stdout - The “standard output” stream for a process (file descriptor 1) is writable by the process, and is used for displaying regular output to the user.
- stderr - The “standard error” stream for a process (file descriptor 2) is writable by the process, and is used for conveying error messages.
$ python -u os_popen.py
popen, read:
stdout: 'to stdout\n'
popen, write:
stdin: to stdin
The caller can only read from or write to the streams associated with the child process, which limits the usefulness. The other popen() variants provide additional streams so it is possible to work with stdin, stdout, and stderr as needed.
For example, popen2() returns a write-only stream attached to stdin of the child process, and a read-only stream attached to its stdout.
import os
print 'popen2:'
pipe_stdin, pipe_stdout = os.popen2('cat -')
try:
pipe_stdin.write('through stdin to stdout')
finally:
pipe_stdin.close()
try:
stdout_value = pipe_stdout.read()
finally:
pipe_stdout.close()
print '\tpass through:', repr(stdout_value)
This simplistic example illustrates bi-directional communication. The value written to stdin is read by cat (because of the '-' argument), then written back to stdout. A more complicated process could pass other types of messages back and forth through the pipe; even serialized objects.
$ python -u os_popen2.py
popen2:
pass through: 'through stdin to stdout'
In most cases, it is desirable to have access to both stdout and stderr. The stdout stream is used for message passing and the stderr stream is used for errors, so reading from it separately reduces the complexity for parsing any error messages. The popen3() function returns three open streams tied to stdin, stdout, and stderr of the new process.
import os
print 'popen3:'
pipe_stdin, pipe_stdout, pipe_stderr = os.popen3('cat -; echo ";to stderr" 1>&2')
try:
pipe_stdin.write('through stdin to stdout')
finally:
pipe_stdin.close()
try:
stdout_value = pipe_stdout.read()
finally:
pipe_stdout.close()
print '\tpass through:', repr(stdout_value)
try:
stderr_value = pipe_stderr.read()
finally:
pipe_stderr.close()
print '\tstderr:', repr(stderr_value)
Notice that the program has to read from and close both stdout and stderr separately. There are some related to flow control and sequencing when dealing with I/O for multiple processes. The I/O is buffered, and if the caller expects to be able to read all of the data from a stream then the child process must close that stream to indicate the end-of-file. For more information on these issues, refer to the Flow Control Issues section of the Python library documentation.
$ python -u os_popen3.py
popen3:
pass through: 'through stdin to stdout'
stderr: ';to stderr\n'
And finally, popen4() returns 2 streams, stdin and a merged stdout/stderr. This is useful when the results of the command need to be logged, but not parsed directly.
import os
print 'popen4:'
pipe_stdin, pipe_stdout_and_stderr = os.popen4('cat -; echo ";to stderr" 1>&2')
try:
pipe_stdin.write('through stdin to stdout')
finally:
pipe_stdin.close()
try:
stdout_value = pipe_stdout_and_stderr.read()
finally:
pipe_stdout_and_stderr.close()
print '\tcombined output:', repr(stdout_value)
All of the messages written to both stdout and stderr are read together.
$ python -u os_popen4.py
popen4:
combined output: 'through stdin to stdout;to stderr\n'
Besides accepting a single string command to be given to the shell for parsing, popen2(), popen3(), and popen4() also accept a sequence of strings (command, followed by arguments).
import os
print 'popen2, cmd as sequence:'
pipe_stdin, pipe_stdout = os.popen2(['cat', '-'])
try:
pipe_stdin.write('through stdin to stdout')
finally:
pipe_stdin.close()
try:
stdout_value = pipe_stdout.read()
finally:
pipe_stdout.close()
print '\tpass through:', repr(stdout_value)
In this case, the arguments are not processed by the shell.
$ python -u os_popen2_seq.py
popen2, cmd as sequence:
pass through: 'through stdin to stdout'
File Descriptors¶
os includes the standard set of functions for working with low-level file descriptors (integers representing open files owned by the current process). This is a lower-level API than is provided by file objects. They are not covered here because it is generally easier to work directly with file objects. Refer to the library documentation for details.
Filesystem Permissions¶
The function access() can be used to test the access rights a process has for a file.
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)
The results will vary depending on how the example code is installed, but it will look something like this:
$ python os_access.py
Testing: os_access.py
Exists: True
Readable: True
Writable: True
Executable: False
The library documentation for access() includes two special warnings. First, there isn’t much sense in calling access() to test whether a file can be opened before actually calling open() on it. There is a small, but real, window of time between the two calls during which the permissions on the file could change. The other warning applies mostly to networked filesystems that extend the POSIX permission semantics. Some filesystem types may respond to the POSIX call that a process has permission to access a file, then report a failure when the attempt is made using open() for some reason not tested via the POSIX call. All in all, it is better to call open() with the required mode and catch the IOError raised if there is a problem.
More detailed information about the file can be accessed using stat() or lstat() (for checking the status of something that might be a symbolic link).
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 '\tSize:', stat_info.st_size
print '\tPermissions:', oct(stat_info.st_mode)
print '\tOwner:', stat_info.st_uid
print '\tDevice:', stat_info.st_dev
print '\tLast modified:', time.ctime(stat_info.st_mtime)
Once again, the output will vary depending on how the example code was installed. Try passing different filenames on the command line to os_stat.py.
$ python os_stat.py
os.stat(os_stat.py):
Size: 1516
Permissions: 0100644
Owner: 527
Device: 234881026
Last modified: Fri Feb 11 08:38:29 2011
On Unix-like systems, file permissions can be changed using chmod(), passing the mode as an integer. Mode values can be constructed using constants defined in the stat module. This example toggles the user’s execute permission bit:
import os
import stat
filename = 'os_stat_chmod_example.txt'
if os.path.exists(filename):
os.unlink(filename)
f = open(filename, 'wt')
f.write('contents')
f.close()
# 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)
The script assumes it has the permissions necessary to modify the mode of the file when run.
$ python os_stat_chmod.py
Adding execute permission
Directories¶
There are several functions for working with directories on the filesystem, including creating, listing contents, and removing them.
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
f = open(file_name, 'wt')
try:
f.write('example file')
finally:
f.close()
print 'Listing', dir_name
print os.listdir(dir_name)
print 'Cleaning up'
os.unlink(file_name)
os.rmdir(dir_name)
There are two sets of functions for creating and deleting directories. When creating a new directory with mkdir(), all of the parent directories must already exist. When removing a directory with rmdir(), only the leaf directory (the last part of the path) is actually removed. In contrast, makedirs() and removedirs() operate on all of the nodes in the path. makedirs() will create any parts of the path which do not exist, and removedirs() will remove all of the parent directories (assuming it can).
$ python os_directories.py
Creating os_directories_example
Creating os_directories_example/example.txt
Listing os_directories_example
['example.txt']
Cleaning up
Symbolic Links¶
For platforms and filesystems that support them, there are functions for working with symlinks.
import os, tempfile
link_name = tempfile.mktemp()
print 'Creating link %s -> %s' % (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)
Although os includes tempnam() for creating temporary filenames, it is not as secure as the tempfile module and produces a RuntimeWarning message when it is used. In general it is better to use tempfile, as in this example.
$ python os_symlinks.py
Creating link /var/folders/5q/8gk0wq888xlggz008k8dr7180000hg/T/tmpoVkEFr -> os_symlinks.py
Permissions: 0120755
Points to: os_symlinks.py
Walking a Directory Tree¶
The function walk() traverses a directory recursively and for each directory generates a tuple containing the directory path, any immediate sub-directories of that path, and the names of any files in that directory.
import os, 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 '\n', dir_name
# Make the subdirectory names stand out with /
sub_dirs = [ '%s/' % 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 '\t%s' % c
This example shows a recursive directory listing.
$ python os_walk.py ../zipimport
../zipimport
__init__.py
__init__.pyc
example_package/
index.rst
zipimport_example.zip
zipimport_find_module.py
zipimport_find_module.pyc
zipimport_get_code.py
zipimport_get_code.pyc
zipimport_get_data.py
zipimport_get_data.pyc
zipimport_get_data_nozip.py
zipimport_get_data_nozip.pyc
zipimport_get_data_zip.py
zipimport_get_data_zip.pyc
zipimport_get_source.py
zipimport_get_source.pyc
zipimport_is_package.py
zipimport_is_package.pyc
zipimport_load_module.py
zipimport_load_module.pyc
zipimport_make_example.py
zipimport_make_example.pyc
../zipimport/example_package
README.txt
__init__.py
__init__.pyc
Running External Commands¶
Warning
Many of these functions for working with processes have limited portability. For a more consistent way to work with processes in a platform independent manner, see the subprocess module instead.
The simplest way to run a separate command, without interacting with it at all, is system(). It takes a single string which is the command line to be executed by a sub-process running a shell.
import os
# Simple command
os.system('ls -l')
The return value of system() is the exit value of the shell running the program packed into a 16 bit number, with the high byte the exit status and the low byte the signal number that caused the process to die, or zero.
$ python -u os_system_example.py
total 248
-rw-r--r-- 1 dhellmann dhellmann 0 Feb 11 2011 __init__.py
-rw-r--r-- 1 dhellmann dhellmann 22700 Feb 17 10:13 index.rst
-rw-r--r-- 1 dhellmann dhellmann 1360 Feb 11 2011 os_access.py
-rw-r--r-- 1 dhellmann dhellmann 1292 Feb 11 2011 os_cwd_example.py
-rw-r--r-- 1 dhellmann dhellmann 1499 Feb 11 2011 os_directories.py
-rw-r--r-- 1 dhellmann dhellmann 1573 Feb 11 2011 os_environ_example.py
-rw-r--r-- 1 dhellmann dhellmann 1241 Feb 11 2011 os_exec_example.py
-rw-r--r-- 1 dhellmann dhellmann 1267 Feb 11 2011 os_fork_example.py
-rw-r--r-- 1 dhellmann dhellmann 1703 Feb 11 2011 os_kill_example.py
-rw-r--r-- 1 dhellmann dhellmann 1476 Feb 11 2011 os_popen.py
-rw-r--r-- 1 dhellmann dhellmann 1506 Feb 11 2011 os_popen2.py
-rw-r--r-- 1 dhellmann dhellmann 1528 Feb 11 2011 os_popen2_seq.py
-rw-r--r-- 1 dhellmann dhellmann 1658 Feb 11 2011 os_popen3.py
-rw-r--r-- 1 dhellmann dhellmann 1567 Feb 11 2011 os_popen4.py
-rw-r--r-- 1 dhellmann dhellmann 1395 Feb 11 2011 os_process_id_example.py
-rw-r--r-- 1 dhellmann dhellmann 1896 Feb 11 2011 os_process_user_example.py
-rw-r--r-- 1 dhellmann dhellmann 1206 Feb 11 2011 os_spawn_example.py
-rw-r--r-- 1 dhellmann dhellmann 1516 Feb 11 2011 os_stat.py
-rw-r--r-- 1 dhellmann dhellmann 1751 Feb 11 2011 os_stat_chmod.py
-rwxr--r-- 1 dhellmann dhellmann 8 Feb 17 11:32 os_stat_chmod_example.txt
-rw-r--r-- 1 dhellmann dhellmann 1421 Feb 11 2011 os_symlinks.py
-rw-r--r-- 1 dhellmann dhellmann 1250 Feb 11 2011 os_system_background.py
-rw-r--r-- 1 dhellmann dhellmann 1191 Feb 11 2011 os_system_example.py
-rw-r--r-- 1 dhellmann dhellmann 1214 Feb 11 2011 os_system_shell.py
-rw-r--r-- 1 dhellmann dhellmann 1499 Feb 11 2011 os_wait_example.py
-rw-r--r-- 1 dhellmann dhellmann 1555 Feb 11 2011 os_waitpid_example.py
-rw-r--r-- 1 dhellmann dhellmann 1643 Feb 11 2011 os_walk.py
Since the command is passed directly to the shell for processing, it can even include shell syntax such as globbing or environment variables:
import os
# Command with shell expansion
os.system('ls -ld $TMPDIR')
$ python -u os_system_shell.py
drwx------ 434 dhellmann dhellmann 14756 Feb 17 11:32 /var/folders/5q/8gk0wq888xlggz008k8dr7180000hg/T/
Unless the command is explicitly run in the background, the call to system() blocks until it is complete. Standard input, output, and error from the child process are tied to the appropriate streams owned by the caller by default, but can be redirected using shell syntax.
import os
import time
print 'Calling...'
os.system('date; (sleep 3; date) &')
print 'Sleeping...'
time.sleep(5)
This is getting into shell trickery, though, and there are better ways to accomplish the same thing.
$ python -u os_system_background.py
Calling...
Sun Feb 17 11:32:53 EST 2013
Sleeping...
Sun Feb 17 11:32:56 EST 2013
Creating Processes with os.fork()¶
The POSIX functions fork() and exec*() (available under Mac OS X, Linux, and other UNIX variants) are exposed via the os module. Entire books have been written about reliably using these functions, so check the library or bookstore for more details than are presented here.
To create a new process as a clone of the current process, use fork():
import os
pid = os.fork()
if pid:
print 'Child process id:', pid
else:
print 'I am the child'
The output will vary based on the state of the system each time the example is run, but it will look something like:
$ python -u os_fork_example.py
Child process id: 71388
I am the child
After the fork, there are two processes running the same code. For a program to tell which one it is in, it needs to check the return value of fork(). If the value is 0, the current process is the child. If it is not 0, the program is running in the parent process and the return value is the process id of the child process.
From the parent process, it is possible to send the child signals. This is a bit more complicated to set up, and uses the signal module. First, define a signal handler to be invoked when the signal is received.
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 %s' % pid
Then fork(), and in the parent pause a short amount of time before sending a USR1 signal using kill(). The short pause gives the child process time to set up the signal handler.
print 'Forking...'
child_pid = os.fork()
if child_pid:
print 'PARENT: Pausing before sending signal...'
time.sleep(1)
print 'PARENT: Signaling %s' % child_pid
os.kill(child_pid, signal.SIGUSR1)
In the child, set up the signal handler and go to sleep for a while to give the parent time to send the signal:
else:
print 'CHILD: Setting up signal handler'
signal.signal(signal.SIGUSR1, signal_usr1)
print 'CHILD: Pausing to wait for signal'
time.sleep(5)
A real application, wouldn’t need (or want) to call sleep().
$ python os_kill_example.py
Forking...
PARENT: Pausing before sending signal...
PARENT: Signaling 71391
Forking...
CHILD: Setting up signal handler
CHILD: Pausing to wait for signal
Received USR1 in process 71391
A simple way to handle separate behavior in the child process is to check the return value of fork() and branch. More complex behavior may call for more code separation than a simple branch. In other cases, there may be an existing program that needs to be wrapped. For both of these situations, the exec*() series of functions can be used to run another program.
import os
child_pid = os.fork()
if child_pid:
os.waitpid(child_pid, 0)
else:
os.execlp('ls', 'ls', '-l', '/tmp/')
When a program is “execed”, the code from that program replaces the code from the existing process.
$ python os_exec_example.py
total 64
-rw-r----- 1 root _lp 1193 Feb 17 11:17 10b2351220ffe
drwxr-xr-x 3 root wheel 102 Jan 30 12:07 20130130120730
drwxr-xr-x 3 root wheel 102 Feb 5 19:08 20130205190804
-rw-r--r-- 1 dhellmann wheel 0 Feb 13 14:05 CrashReportCopyLock-(null)
-rw-r--r-- 1 dhellmann wheel 0 Feb 13 23:27 CrashReportCopyLock-iPhone
-rw-r--r-- 1 dhellmann wheel 12288 Feb 17 11:32 example.db
drwx------ 3 dhellmann wheel 102 Feb 8 03:15 launch-GrELOT
drwx------ 3 dhellmann wheel 102 Jan 10 17:08 launch-b55fAc
drwx------ 3 dhellmann wheel 102 Jan 10 17:08 launch-k5e7k9
drwx------ 3 dhellmann wheel 102 Jan 10 17:08 launch-xK9lQX
drwx------ 3 _spotlight wheel 102 Feb 6 19:30 launchd-13759.Krlfzj
drwx------ 3 dhellmann wheel 102 Jan 10 17:08 launchd-369.kLxyQv
-rw-r--r-- 1 dhellmann wheel 12288 Feb 17 10:07 pymotw_import_example.shelve
-rw-r--r-- 1 dhellmann wheel 448 Feb 17 10:07 trace_example.recurse.cover
drwxr-xr-x 2 dhellmann dhellmann 68 Feb 15 03:15 var_backups
There are many variations of exec*(), depending on the form in which the arguments are available, whether the path and environment of the parent process should be be copied to the child, etc. Refer to the library documentation for complete details.
For all variations, the first argument is a path or filename and the remaining arguments control how that program runs. They are either passed as command line arguments or override the process “environment” (see os.environ and os.getenv).
Waiting for a Child¶
Many computationally intensive programs use multiple processes to work around the threading limitations of Python and the Global Interpreter Lock. When starting several processes to run separate tasks, the master will need to wait for one or more of them to finish before starting new ones, to avoid overloading the server. There are a few different ways to do that using wait() and related functions.
When it does not matter which child process might exit first, use wait(). It returns as soon as any child process exits.
import os
import sys
import time
for i in range(3):
print 'PARENT: Forking %s' % i
worker_pid = os.fork()
if not worker_pid:
print 'WORKER %s: Starting' % i
time.sleep(2 + i)
print 'WORKER %s: Finishing' % i
sys.exit(i)
for i in range(3):
print 'PARENT: Waiting for %s' % i
done = os.wait()
print 'PARENT:', done
The return value from wait() is a tuple containing the process id and exit status (“a 16-bit number, whose low byte is the signal number that killed the process, and whose high byte is the exit status”).
$ python os_wait_example.py
PARENT: Forking 0
WORKER 0: Starting
WORKER 0: Finishing
PARENT: Forking 0
PARENT: Forking 1
WORKER 1: Starting
WORKER 1: Finishing
PARENT: Forking 0
PARENT: Forking 1
PARENT: Forking 2
WORKER 2: Starting
WORKER 2: Finishing
PARENT: Forking 0
PARENT: Forking 1
PARENT: Forking 2
PARENT: Waiting for 0
PARENT: (71397, 0)
PARENT: Waiting for 1
PARENT: (71398, 256)
PARENT: Waiting for 2
PARENT: (71399, 512)
To wait for a specific process, use waitpid().
import os
import sys
import time
workers = []
for i in range(3):
print 'PARENT: Forking %s' % i
worker_pid = os.fork()
if not worker_pid:
print 'WORKER %s: Starting' % i
time.sleep(2 + i)
print 'WORKER %s: Finishing' % i
sys.exit(i)
workers.append(worker_pid)
for pid in workers:
print 'PARENT: Waiting for %s' % pid
done = os.waitpid(pid, 0)
print 'PARENT:', done
Pass the process id of the target process, and waitpid() blocks until that process exits.
$ python os_waitpid_example.py
PARENT: Forking 0
WORKER 0: Starting
WORKER 0: Finishing
PARENT: Forking 0
PARENT: Forking 1
WORKER 1: Starting
WORKER 1: Finishing
PARENT: Forking 0
PARENT: Forking 1
PARENT: Forking 2
WORKER 2: Starting
WORKER 2: Finishing
PARENT: Forking 0
PARENT: Forking 1
PARENT: Forking 2
PARENT: Waiting for 71402
PARENT: (71402, 0)
PARENT: Waiting for 71403
PARENT: (71403, 256)
PARENT: Waiting for 71404
PARENT: (71404, 512)
wait3() and wait4() work in a similar manner, but return more detailed information about the child process with the pid, exit status, and resource usage.
Spawn¶
As a convenience, the spawn*() family of functions handles the fork() and exec*() in one statement:
import os
os.spawnlp(os.P_WAIT, 'ls', 'ls', '-l', '/tmp/')
The first argument is a mode indicating whether or not to wait for the process to finish before returning. This example waits. Use P_NOWAIT to let the other process start, but then resume in the current process.
$ python os_spawn_example.py
total 56
drwxr-xr-x 3 root wheel 102 Jan 30 12:07 20130130120730
drwxr-xr-x 3 root wheel 102 Feb 5 19:08 20130205190804
-rw-r--r-- 1 dhellmann wheel 0 Feb 13 14:05 CrashReportCopyLock-(null)
-rw-r--r-- 1 dhellmann wheel 0 Feb 13 23:27 CrashReportCopyLock-iPhone
-rw-r--r-- 1 dhellmann wheel 12288 Feb 17 11:32 example.db
drwx------ 3 dhellmann wheel 102 Feb 8 03:15 launch-GrELOT
drwx------ 3 dhellmann wheel 102 Jan 10 17:08 launch-b55fAc
drwx------ 3 dhellmann wheel 102 Jan 10 17:08 launch-k5e7k9
drwx------ 3 dhellmann wheel 102 Jan 10 17:08 launch-xK9lQX
drwx------ 3 _spotlight wheel 102 Feb 6 19:30 launchd-13759.Krlfzj
drwx------ 3 dhellmann wheel 102 Jan 10 17:08 launchd-369.kLxyQv
-rw-r--r-- 1 dhellmann wheel 12288 Feb 17 10:07 pymotw_import_example.shelve
-rw-r--r-- 1 dhellmann wheel 448 Feb 17 10:07 trace_example.recurse.cover
drwxr-xr-x 2 dhellmann dhellmann 68 Feb 15 03:15 var_backups
See also
- os
- Standard library documentation for this module.
- subprocess
- The subprocess module supersedes os.popen().
- multiprocessing
- The multiprocessing module makes working with extra processes easier than doing all of the work yourself.
- tempfile
- The tempfile module for working with temporary files.
- Unix Manual Page Introduction
Includes definitions of real and effective ids, etc.
- Speaking UNIX, Part 8.
Learn how UNIX multitasks.
http://www.ibm.com/developerworks/aix/library/au-speakingunix8/index.html
- Unix Concepts
For more discussion of stdin, stdout, and stderr.
- Delve into Unix Process Creation
Explains the life cycle of a UNIX process.
http://www.ibm.com/developerworks/aix/library/au-unixprocess.html
- Advanced Programming in the UNIX(R) Environment
- Covers working with multiple processes, such as handling signals, closing duplicated file descriptors, etc.