process control -- fork-exec and pipe with I/O redirection

Design goals:
  * Easy to fork-exec commands, wait or no wait
  * Easy to capture stdout/stderr of children (command substitution)
  * Easy to express I/O redirections
  * Easy to construct pipelines
  * Use short names for easy interactive typing

In effect, make Python more usable as a system shell.

Technically, pc.py is a layer on top of subprocess. The subprocess
module support a rich API but is clumsy for many common use cases,
namely sync/async fork-exec, command substitution and pipelining,
all of which is trivial to do on system shells. [1][2]

The main interpreter process had better be a single thread, since
forking multithreaded programs is not well understood by mortals. [3]

This module depends on Python 2.6, or where subprocess is available.
Doctests require /bin/sh to pass. Tested on Linux.

This is an alpha release. Some features are unimplemented. Expect bugs.


[1] sh(1) -- http://heirloom.sourceforge.net/sh/sh.1.html
[2] The Scheme Shell -- http://www.scsh.net/docu/html/man.html
[3] http://golang.org/src/pkg/syscall/exec_unix.go


Let's start forking!


Cmd.run(), Cmd.spawn() and Cmd.capture()
========================================

Cmd objects hold fork-exec info and can be run(), spawn()'ed or capture()'d.


Cmd.run() performs a fork-exec-wait, e.g.

    >>> exit_status = Cmd('dmesg').run()

is equivalent this "normal" shell's command:

    $ dmesg

Sh(cmd, **kwargs) is equivalent to Cmd(['/bin/sh', '-c', cmd], **kwargs).


Cmd.spawn() performs an fork-exec (no wait) and returns a subprocess.Popen object, e.g.

    >>> proc = Cmd(['gvim', '-f']).spawn()

is equivalent to this shell's ampersand command:

    $ gvim -f &

spawn(*) is equivalent to Cmd(*).spawn(). Have fun with proc.kill() :)


Cmd.captured() also performs a fork-exec-wait but returns a subset of
the child's [stdout, stdin] as file object(s), e.g.

    >>> out = Sh('echo -n foo').capture(1).read()
    >>> err = Sh('echo -n foo >&2').capture(2).read()
    >>> out, err = Sh('echo -n foo; echo -n bar >&2').capture(1, 2)

This is equivalent to shell backquotes aka command substitution (which
cannot capture stderr separately):

    $ out=`echo -n foo`
    $ outerr=$(echo -n foo; echo -n bar 2>&1 >&2)

cmd(*) returns Cmd(*).capture(1).read() wrapped in a try: ... finally: close() clause.
sh(*) returns Sh(*).capture(1).read() wrapped in a try: ... finally: close() clause.


I/O redirection
===============

I/O redirections are performed by specifying a dict mapping file
descriptors to either open files, strings, or other file descriptors, e.g.

    >>> out = Sh('echo -n foo; echo -n bar >&2', fd={2: 1}).capture(1).read()

or more succintly (and safer):

    >>> out = sh('echo -n foo; echo -n bar >&2', {2: 1})

The following append the child's stdout to the file 'abc' (equiv. to 'echo foo >> abc')

    >>> out = sh('echo foo', {1: open('abc', 'a')})

os.devnull (which is just '/dev/null' on Unix) also works:

    >>> out = sh('echo bogus stuff', {1: os.devnull})

In fact you can pass in fd=SILIENCE, which send everything
straight to hell, hmm...  I mean /dev/null.


Pipe
====

Pipe are constructed by a list of Cmd's. Pipes can also be run(), spawn()'ed or capture()'d, e.g.

    >>> exit_status = Pipe(Cmd('dmesg'), Cmd('grep x')).run()
    
    >>> out = Pipe(Cmd('dmesg'), Cmd('grep x')).capture().read()

pipe(*) returns Pipe(*).capture(1).read() wrapped in a try: ... finally: close() clause, e.g. the following

    >>> item = pipe(Cmd('find -mmin +30'), Cmd('dmenu'))

finds files modified in the last 30 minutes and pipes to dmenu to select a single item.


API REFERENCE
=============

See docstrings and doctests for now.
