Skip to content

janestreet/shexp

Repository files navigation

./images/logo.png

Shexp is composed of two parts: a library providing a process monad for shell scripting in OCaml as well as a simple s-expression based shell interpreter. Both provide good debugging support.

Shexp works on both Unix and Windows and depends only on Base.

The Shexp_process library

The Shexp_process library exposes a single Process module allowing one to construct complex pipelines such as pipes and other redirections. It is intended to replace shell scripts as well as provide a more complete alternative to Async.Process like modules.

On Unix, Shexp_process uses the specific *at system calls (such as openat) to reliably maintain several working directories inside the same system process as well as vfork to avoid performance problems with large processes.

Usage

One creates a value of type 'a Process.t which represent a process pipeline. Using the combinators of the process module, one can modify the execution environment (current working directory, environment variables, …) as well as construct complex redirections.

To effectively execute the pipeline and get a result, one has to call Process.eval.

Essentially you get the same primitives as what you would get from a shell, except that everything is typed:

(** Run an external program *)
val run : string -> string list -> unit t

(** Equivalent of a shell pipe *)
val pipe : unit t -> 'a t -> 'a t

(** Same thing, but you get the values from both sides of the pipe *)
val pipe_both : 'a t -> 'b t -> ('a * 'b) t

(** Read all of the process' standard input  *)
val read_all : string t

(** [chdir dir k] executes [k] with the current directory set to [dir] *)
val chdir : string -> 'a t -> 'a t

For instance, to run an external command in a given directory and capture its standard output, where |- is infix operator for pipe:

let f ~dir prog args = eval (chdir dir (run prog args |- read_all))

Debugging

Shexp_process allows one to plug a debugger in the evaluator. A debugger is essentially a set of hooks called at the appropriate places by Shexp_process. Shexp_process itself provides two non-interactive debuggers: a logger and a tracer.

The logger is intended for printing command synchrously as they are a run, a bit like set -x in bash.

The tracer provides a full trace of execution. The trace is presented as a tree, so you can see clearly what happens on both sides of a fork.

For instance the following process:

echo "Hello, world!" |- run "blah" []

would produce the following trace:

((create-pipe)
 (-> (4 5))
 (fork
   (
    (do
      (set-ios (stdout) 5)
      (echo "Hello, world!"))
    (close-fd 36)
   )
   (
    (do
      (set-ios (stdin) 4)
      (run blah ())
      (raised (Failure "blah: command not found")))
    (close-fd 35)
   )
 )
)