Ocamlnet 3 Reference Manual : Shell

# Module Shell

    module Shell: sig .. end

Calls external programs, creates pipelines, etc. (simplified interface)

Signal handlers: When you call the function `call`, signal handlers are automatically installed by `Shell_sys.install_job_handlers`, unless this installation has already been performed. You can configure these handlers by `Shell_sys.configure_job_handlers`. The handlers remain in effect even after `call` returns.

Note that this has a global side effect on the whole process, because there is only one set of signal handlers.

# Calling commands and pipelines

The following functions are simplified versions of the `Shell_sys.job` abstraction.

    exception Subprocess_error of (string * Unix.process_status) list

The string contains the called commands in a readable representation. The list enumerates the return codes of the processes that have been started for the commands.

    type producer

A producer generates data sent to a called process

    type consumer

A consumer receives data from a called process

    type assignment

An assignment redirects file descriptors while calling a process

    val command : ?cmdname:string ->
           ?arguments:string array ->
           ?chdir:string ->
           ?environment:Shell_sys.environment ->
           ?descriptors:Unix.file_descr list ->
           ?assignments:assignment list -> string -> Shell_sys.command

Creates a command descriptor, to be used in `call`. The anonymous string argument is the name of the executable to invoke. If the name contains a '/', it is simply interpreted as the filename of the executable. Otherwise the command is searched in the current PATH.

`cmdname` : The name of the command passed in `argv[0]`. By default, this argument is derived from the name of the executable.
`arguments` : The arguments of the command (starting with the first real argument, skipping `cmdname`). By default `[]` .
`chdir` : Before the command is executed it is changed to this directory.
`environment` : The environment of the command. By default, the current environment
`descriptors` : The list of file descriptors to share with the current process; all other file descriptors will be closed. By default, `[stdin; stdout; stderr]` .
`assignments` : The list of descriptor assignments. The assignments are applied one after the other. By default empty.

    val cmd : ?cmdname:string ->
           ?chdir:string ->
           ?environment:Shell_sys.environment ->
           ?descriptors:Unix.file_descr list ->
           ?assignments:assignment list ->
           string -> string list -> Shell_sys.command

The same as `command` but with a slightly different interface: Use

     cmd "ls" [ "/dir/file" ]

instead of

     command ~arguments:[|"/dir/file"|] "ls"

The named arguments have the same meanings as in `command`.

    val call : ?ignore_error_code:bool ->
           ?mode:Shell_sys.group_mode ->
           ?stdin:producer ->
           ?stdout:consumer ->
           ?stderr:consumer -> Shell_sys.command list -> unit

Starts the pipeline represented by the list of commands; i.e. if `[c1;c2;...;cN]` is passed, this corresponds to the pipeline `c1 | c2 | ... | cN` (in shell notation).

The function returns normally if all processes can be started and terminate regularly with exit code 0. If a process terminates with some other exit code, and `ignore_error_code` is set, the function returns normally, too. The latter does not apply if a process terminates because of a signal (which triggers always the exception `Subprocess_error`).

If a process terminates with an exit code other than 0 and `ignore_error_code` is not set (the default), or if a process is terminated because of a signal, the exception `Subprocess_error` will be raised. For every command the process result is included in the exception argument.

If a process cannot be started (e.g. because of insufficient resources), the function will try to shut down the already running part of the pipeline by sending SIGTERM to these processes. It is not checked whether the processes actually terminate (no "wait" for them); an appropriate exception will be raised. In the case that it is not even possible to perform these cleanup actions, the exception `Shell_sys.Fatal_error` will be raised.

When the function raises an exception other than `Subprocess_error`, a serious error condition has happened, and it is recommended to exit the program as soon as possible.

`ignore_error_code` : If `true`, exit codes other than 0 of the subprocesses are ignored. This does not apply to signals, however. By default `false`.
`mode` : See `Shell_sys.run_job` for a detailed description of this parameter. By default `Same_as_caller`.
`stdin` : If present, the first process of the pipeline reads input data from this procucer. By default, there is no such producer.
`stdout` : If present, the last process of the pipeline writes output data to this consumer. By default, there is no such consumer.
`stderr` : If present, all processes of the pipeline write their error messages to this consumer. By default, there is no such consumer.

    val setup_job : ?stdin:producer ->
           ?stdout:consumer ->
           ?stderr:consumer ->
           Shell_sys.command list -> Shell_sys.job * Unix.file_descr list

Creates a job like `call`, but does not execute it. In addition to the job, the file descriptors are returned that must be closed when the job is done.

    val postprocess_job : ?ignore_error_code:bool -> Shell_sys.job_instance -> unit

Looks at the error codes of the job, and raises `Subprocess_error` when there is an error that cannot be ignored. As error conditions are considered non-zero exit codes of any called processes, or signals terminating any of the called processes.

`ignore_error_code` : If `true`, exit codes other than 0 of the subprocesses are ignored. This does not apply to signals, however. By default `false`.

    val assign : src:Unix.file_descr -> target:Unix.file_descr -> assignment

Arranges a redirection such that writing to `src` or reading from `src` will actually write to `target` or read from `target` (i.e., the `target` descriptor is duplicated and replaces the `src` descriptor just before the process is launched.)

Note that assignments work only if the descriptors are shared with the called process, so they must also be contained in the `descriptors` list of `command` or `cmd`. Furthermore, the close-on-exec flag must not be set.

    val (>&) : Unix.file_descr -> Unix.file_descr -> assignment

Same as `assign`, but infix notation. For example, `stdout >& stderr` creates an assignment such that all output to stdout is redirected to stderr.

`f >& g` is the same as `assign ~src:f target:g`. It should be used for output assignments (as in the Bourne shell).

    val (<&) : Unix.file_descr -> Unix.file_descr -> assignment

Same as `assign`, but infix notation. For example, `stdin <& f` creates an assignment such that the called process reads from the open file descriptor `f`.

`f <& g` is the same as `assign ~src:f target:g`. It should be used for input assignments (as in the Bourne shell).

    val assigned_pair : assignment -> Unix.file_descr * Unix.file_descr

Returns the target and the source of the assignment as pair of descriptors `(target,src)`.

    val stdin : Unix.file_descr

    val stdout : Unix.file_descr

    val stderr : Unix.file_descr

The standard descriptors; defined here for convenience.

    val from_string : ?pos:int -> ?len:int -> ?epipe:(unit -> unit) -> string -> producer

Creates a producer taking the data from a string `s`. After these data are sent, the pipeline is closed.

`pos` : The position in `s` where the data slice to transfer begins. By default `0`.
`len` : The length of the data slice to transfer. By default, all bytes from the start position `pos` to the end of the string are taken.
`epipe` : This function is called when the pipeline breaks (EPIPE). Default: the empty function. EPIPE exceptions are always caught, and implicitly handled by closing the pipeline.

    val from_stream : ?epipe:(unit -> unit) -> string Stream.t -> producer

Creates a producer taking the data from a stream of strings. After the data are sent, the pipeline is closed.

`epipe` : This function is called when the pipeline breaks (EPIPE). Default: the empty function. EPIPE exceptions are always caught, and implicitly handled by closing the pipeline.

    val from_function : producer:(Unix.file_descr -> bool) -> unit -> producer

Creates a producer taking the data from a function. See `Shell_sys.add_producer` for the meaning of the `producer` function.

    val from_file : string -> producer

Creates a producer taking the data from the file whose name is passed to this function.

    val from_fd : Unix.file_descr -> producer

Creates a producer taking the data from the file descriptor passed to this function.

    val from_dev_null : producer

A producer taking the data from `/dev/null`.

    val to_buffer : Buffer.t -> consumer

Creates a consumer writing the data into the passed buffer.

    val to_function : consumer:(Unix.file_descr -> bool) -> unit -> consumer

Creates a consumer writing the data by calling a function. See `Shell_sys.add_consumer` for the meaning of the `consumer` function.

    val to_file : ?append:bool -> string -> consumer

Creates a consumer writing the data into the file whose name is passed to this function. Unless `append` is given, the file is truncated and overwritten. If `append` is `true`, the data are appended to the file. By default, `append` is `false`.

    val to_fd : Unix.file_descr -> consumer

Creates a consumer redirecting the data to the file descriptor

    val to_dev_null : consumer

A consumer redirecting the data to `/dev/null`.

# Examples

The following examples show toploop sessions using `Shell`.

## Invoking simple commands

Call the command "ls" without redirection:

     # call [ command "ls" ];;
     IDEAS       s1.ml~      shell.mli~      shell_sys.ml~  unix_exts.ml
     META        shell.a     shell.ml~       shell_sys.o    unix_exts.mli
     Makefile    shell.cma   shell_sys.cmi   t              unix_exts.mli~
     Makefile~   shell.cmi   shell_sys.cmo   testjob        unix_exts.ml~
     depend      shell.cmo   shell_sys.cmx   testjob~       unix_exts.o
     libshell.a  shell.cmxa  shell_sys.ml    unix_exts.cmi  unix_exts_c.c
     log         shell.ml    shell_sys.mli   unix_exts.cmo  unix_exts_c.c~
     s1.ml       shell.mli   shell_sys.mli~  unix_exts.cmx  unix_exts_c.o
     \- : unit = ()

## Redirecting stdout to a buffer

The output of "ls" is collected in the buffer `b`:

     # let b = Buffer.create 10;;
     val b : Buffer.t = <abstr>
     # call ~stdout:(to_buffer b) [ command "ls" ];;
     \- : unit = ()
     # Buffer.contents b;;
     \- : string =
     "IDEAS\nMETA\nMakefile\nMakefile~\ndepend\n..."

## Subprocess errors are caught and propagated to the caller

Because "/a" does not exist, "ls" will fail. The command writes the message to stderr (not redirected here), and returns with an exit code of 1, triggering an exception:

     # call [ command ~arguments:[| "/a" |] "ls" ];;
     /bin/ls: /a: No such file or directory
     Uncaught exception: Shell.Subprocess_error ["/bin/ls", Unix.WEXITED 1].

## Redirecting stderr, too

Here, the message written to stderr is collected in `b`:

     # Buffer.clear b;;
     \- : unit = ()
     # call ~stderr:(to_buffer b) [ command ~arguments:[| "/a" |] "ls" ];;
     Uncaught exception: Shell.Subprocess_error ["/bin/ls", Unix.WEXITED 1].
     # Buffer.contents b;;
     \- : string = "/bin/ls: /a: No such file or directory\n"

## Pipelines

Here, the output of "cat" becomes the input of "sort":

     # call [ command ~arguments:[|"META"|] "cat"; command "sort" ];;
     archive(byte) = "shell.cma"
     archive(native) = "shell.cmxa"
     description = "Unix shell functions"
     linkopts = "-cclib -lshell"
     requires = "unix str"
     version = "0.0"
     \- : unit = ()

## Combining pipelines and redirections

The same, but the output of "sort" is collected in the buffer `b`:

     # Buffer.clear b;;
     \- : unit = ()
     # call ~stdout:(to_buffer b) [ command ~arguments:[|"META"|] "cat"; command "sort" ];;
     \- : unit = ()
     # Buffer.contents b;;
     \- : string =
     "archive(byte) = \"shell.cma\"\narchive(native) = \"shell.cmxa\"\ndescription = \"Unix shell functions\"\nlinkopts = \"-cclib -lshell\"\nrequires = \"unix str\"\nversion = \"0.0\"\n"

## Redirection from a string

The contents of the string `s` are written to the input of "sort":

     # let s = "f\na\nd\nc\n";;
     val s : string = "f\na\nd\nc\n"
     # call ~stdin:(from_string s) [ command "sort" ];;
     a
     c
     d
     f
     \- : unit = ()

## Combined redirections

It is possible to have several redirections. Here, the string `s` is sorted by "sort", and the output is collected in the buffer `b`:

     # Buffer.clear b;;
     \- : unit = ()
     # call ~stdout:(to_buffer b) ~stdin:(from_string s) [ command "sort" ];;
     \- : unit = ()
     # Buffer.contents b;;
     \- : string = "a\nc\nd\nf\n"

## Redirections combined with assignments

Here, the output and errors of "ls" are both collected in the buffer `b`:

     # Buffer.clear b;;
     \- : unit = ()
     # call ~stdout:(to_buffer b)
            [ command
                ~assignments:[ stderr >& stdout ]
                ~arguments:[| "/a" |]
                "ls" ];;
     Uncaught exception: Shell.Subprocess_error ["/bin/ls", Unix.WEXITED 1].
     # Buffer.contents b;;
     \- : string = "/bin/ls: /a: No such file or directory\n"

## Final notes

Of course, all features can be combined arbitrarily.

Note that error reporting is better than in a traditional shell, because the exit codes of all started commands are returned. (Shells usually only return the exit code of the last command of a pipeline.)

For non-standard pipelines, you can also use the functions in Shell_sys. "call" is a simple concatenation of Shell_sys invocations.