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:
- Text interpolated into
scriptis 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. Whatscriptmeans is up to the platform shell: its quoting rules, builtins, and expansions differ between hosts and between shells. 3. It is notcommand(program, arguments). The program the shell runs is found on the child’sPATH, 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]