Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Shell

Shell: a small vocabulary for script-shaped programs, over Proc and Path.

Nothing here is a second process model. A command is still a Proc.Command, adjusted with the modifiers below, and every run is still one Proc step: run checks the status and keeps what a script needs to report a failure, run_text and run_lines decode the output, and pipe runs a pipeline under check_pipefail. Import it qualified, since its names are the short ones a script reaches for.

import Proc (..)
import Shell as Sh

fn main() : Unit ! {IO} =
  match run_proc(\() -> Sh.run_lines(command("git", ["status", "--short"]))) of
    Ok(changed) => println(Sh.unlines(Sh.grep(".pr", changed)))
    Err(e) => println(Sh.render(e))

The text helpers are pure: lines, unlines, uniq, numbered, and grep work on values a run already returned, so a script’s filtering is ordinary code a test can call without spawning anything.

unsafe_shell is the one way to hand a string to the platform shell, and its name says so. No other function here parses, quotes, or expands a string.

Types

ShellError

type ShellError = Failed(String, ExitStatus, Buf) | NotText(String)

A run that a script treats as failed: the program that failed, how, and its captured error stream; or a program whose output was not UTF-8 text.

Functions and Values

platform_shell

platform_shell : String

The program unsafe_shell hands its script to.

in_dir

in_dir : (Proc.Command, Path.Path) -> Proc.Command

c run in directory p.

set_env

set_env : (Proc.Command, String, String) -> Proc.Command

c with variable k set to v, after the edits it already has.

unset_env

unset_env : (Proc.Command, String) -> Proc.Command

c with variable k removed, after the edits it already has.

clean_env

clean_env : (Proc.Command) -> Proc.Command

c with an environment holding only the variables it sets.

feed_text

feed_text : (Proc.Command, String) -> Proc.Command

c with s as its standard input.

within

within : (Proc.Command, Int) -> Proc.Command

c killed if it is still running after ms milliseconds.

quiet

quiet : (Proc.Command) -> Proc.Command

c with its error stream discarded.

unsafe_shell

unsafe_shell : (String) -> Proc.Command

A command that runs script through platform_shell, as sh -c script.

Use it only when a script needs what only a shell has: expansion, compound syntax, or a tool distributed as shell code. Three things follow from it, and none of them hold for command:

  1. Text interpolated into script is shell syntax. A value that carries a quote, ;, $(...), or a newline runs as code, so a script built from input it does not control is a command injection. Nothing here quotes or sanitizes a string, because no quoting is right for every shell. 2. What script means is up to the platform shell: its quoting rules, builtins, and expansions differ between hosts and between shells. 3. It is not command(program, arguments). The program the shell runs is found on the child’s PATH, after the child’s environment edits, and its arguments are words the shell split, not the list a caller wrote.

run

run : (Proc.Command) -> Result(Buf, Shell.ShellError) ! {Proc.Proc}

The output of c, when it exits with code 0.

run_text

run_text : (Proc.Command) -> Result(String, Shell.ShellError) ! {Proc.Proc}

The output of c as text, when it exits with code 0 and wrote UTF-8.

run_lines

run_lines : (Proc.Command) -> Result(List(String), Shell.ShellError) ! {Proc.Proc}

The output of c as lines, when it exits with code 0 and wrote UTF-8.

exits_ok

exits_ok : (Proc.Command) -> Bool ! {Proc.Proc}

True when c exits with code 0.

pipe

pipe : (List(Proc.Command)) -> Result(Buf, Shell.ShellError) ! {Proc.Proc}

The output of the pipeline cs, when every stage exits with code 0; else the last stage that did not, as a shell under pipefail reports it.

render

render : (Shell.ShellError) -> String

e as one line for a person: the program, what happened to it, and the first line of what it wrote to its error stream.

describe

describe : (Proc.ExitStatus) -> String

How a run ended, in words.

which

which : (String, List(Path.Path)) -> Option(Path.Path) ! {FileSystem}

The first directory in dirs holding a file called name, as that file’s path. A name that is not a single path component is never searched for, and neither is a directory that is not absolute, as for Proc’s own search. The file is only known to exist: whether it may be executed is the host’s answer when it is run.

search_path

search_path : (String) -> List(Path.Path)

The absolute directories of a PATH-style value, in order. Empty and relative entries are dropped.

lines

lines : (String) -> List(String)

The lines of s. A newline ends a line rather than separating two, so a final newline does not add an empty line and text without one still ends in a line. A carriage return is kept as part of its line.

import Shell (lines)
lines("a\nb\n")
[a, b]

unlines

unlines : (List(String)) -> String

xs as text, each line ended by a newline. lines(unlines(xs)) is xs for lines without a newline of their own.

uniq

uniq : (List(String)) -> List(String)

xs without adjacent repeats, as uniq prints them.

import Shell (uniq)
uniq(["a", "a", "b", "a"])
[a, b, a]

numbered

numbered : (List(String)) -> List((Int, String))

Each of xs with its line number, counted from 1.

grep

grep : (String, List(String)) -> List(String)

The lines of xs that contain needle.

import Shell (grep)
grep("an", ["banana", "cherry", "mango"])
[banana, mango]