Osh's Python Application Programming Interface

Osh was designed to be used from the command-line, combining with a native shell (such as bash) to create an object-oriented shell. A Python API has been added so that the full capabilities of osh are also available from within Python code. Osh's integration of database access, remote execution and general data manipulation is useful in Python scripts as well as from the command-line and from other shell scripts.

API Usage

In order to use osh from within python code, simply import the osh API, e.g.
    #!/usr/bin/python

    from osh.api import *

    osh(gen(10), f(lambda x: x**2), out())
The last line of this script works as follows: This style of osh usage does not provide any convenient way of grabbing the objects generated by the last command, (or any other command for that matter), and making those objects available to code outside the dynamic scope of the osh invocation.

One way to fix this is to write a function which collects the output, and execute it using the f command:

    #!/usr/bin/python
    
    from osh.api import *

    squares = []
    def collect_output(x):
        squares.append(x)
    
    osh(gen(10), f(lambda x: x**2), f(collect_output))
    print squares

The osh API provides a simpler alternative. If the last command in the command sequence is return_list, then the output from the previous command is accumulated and returned as the value of the osh invocation, e.g.

    #!/usr/bin/python
    
    from osh.api import *

    print osh(gen(10), f(lambda x: x**2), return_list())

To pass lines of text from stdin to osh, use the stdin command. For example, the following script can be used to print sorted input:

    #!/usr/bin/python

    from osh.api import *

    osh(stdin(), sort(), out())

Remote Execution

Remote execution is done using the remote command, (instead of the special syntactic form used by the CLI: osh @cluster [ ... ] ...). For example, this script runs the Unix date command on each node of cluster fred:
    #!/usr/bin/python

    from osh.api import *

    osh(remote("fred", sh("date"), out()))
The output from this script might look like this:
    ('101', 'Mon Aug  6 23:03:26 EDT 2007')
    ('102', 'Mon Aug  6 23:03:26 EDT 2007')
    ('103', 'Mon Aug  6 23:03:27 EDT 2007')

The osh command executes sh date on each node of cluster fred. sh is an escape to a native shell, so sh date runs the date command on each node. Output from sh date would normally be a single value, e.g. 'Mon Aug 6 23:03:26 EDT 2007', but remote execution prepends the name of the node that generated the output, (in this case there are three such nodes, 101, 102 and 103).

If the command sequence to be executed remotely has more than one command, then the commands need to be organized into a list. For example, this script finds all java processes running on each node of cluster fred:

    #!/usr/bin/python

    from osh.api import *

    osh(remote("fred",
               [f(processes),
                expand(),
                select('p: "java" in p.command_line()')]),
        out())
The list of remotely executed commands works as follows:

Using Python Functions in the Osh API

In the osh CLI, functions are written as strings, e.g. 'x: x + 1'. This syntax is accepted by the osh API, but functions can also be written as ordinary lambda expressions. So f(lambda x: x + 1) and f('x: x + 1') work interchangeably.

However, there is one situation in which the string form of function is required. If a function is to be executed remotely, then the function must be written using a string. (The reason for this is that command sequences to be executed remotely are pickled and sent to each node over ssh. Strings can be pickled but functions cannot be.)

Error and Exception Handling

If something goes wrong during the execution of an osh command, an exception or error handler is invoked. For example, division by zero raises the ZeroDivisionError exception. The default exception handler prints a description of the problem to stderr. Example:
    #!/usr/bin/python

    from osh.api import *
    
    osh(gen(3), f(lambda x: x / (x-1)), out())
The output from this script is:
    (0,)
    f#2[<function <lambda> at 0x5b0f0>](1) exceptions.ZeroDivisionError: integer division or modulo by zero
    (2,)
gen(3) generates the integers 0, 1 and 2. These integers are piped to the function lambda x: x / (x-1) which raises ZeroDivisionError for x = 1. The first and third lines of output show the expected output for x = 0 and 2, printed to stdout. The middle line goes to stderr:

A similar mechanism is used in case an osh command writes to stderr. Each line going to stderr is passed to an error handler, and the default error handler adds context information and writes to osh's stderr. (Only the sh command is currently capable of invoking this mechanism, as it is the only command that can write to stderr.)

The default exception and error handlers can be overridden. This can be done by invoking set_exception_handler and set_error_handlerf from .oshrc. (See the documentation on the module osh.error for details.)