Packages

process

Run other programs and read their output, start background jobs, and inspect the current process: its arguments, folder and exit code.

import "std:process"

process is how a program talks to the operating system's processes. Use it to run other programs (git, ffmpeg, a shell script) and use what they print, to start a long job in the background, to read the program's own command-line arguments and working directory, and to exit with a status code.

osl
import "std:process"

The examples run in a small sandbox, so they stick to sh, echo, printf and cat, which every Unix system has.

Run a command and get its output#

process.exec(command, args...) runs a command, waits for it and returns what it printed to standard output, as a result. Arguments are passed straight to the program, without a shell.

main.osl
import "std:process"

string out = process.exec("echo", "hello", "from", "echo").unwrap()
log out.trim()
log out.len
Click the code to edit it

Output keeps its trailing newline, so trim() it when you want just the text. Because there is no shell, nothing is interpreted: *, $HOME, | and quotes are plain text.

main.osl
import "std:process"

log process.exec("echo", "$HOME", "*", "a|b").unwrap().trim()
Click the code to edit it

Try it: run printf with "%s-%s" and two arguments.

When the command fails#

exec is strict: a command that exits with a non-zero code is an error (RuntimeError), and its standard error becomes the message. A program that doesn't exist is a NotFoundError.

main.osl
import "std:process"

switch process.exec("sh", "-c", "echo oops >&2; exit 3") (
  case ok(out)
    log "fine"
  case err(e)
    log e.kind ":" e.message.trim()
)

log process.exec("no-such-program-xyz").unwrapErr().kind
Click the code to edit it

Run and inspect how it finished#

When you need to know the exit code, or want to treat failure as normal, use process.run(command, args, options). It always returns an object with ok, code, stdout, stderr and truncated, even when the command fails. Here args is an array.

main.osl
import "std:process"

auto r = process.run("sh", ["-c", "echo out; echo err >&2; exit 2"]).unwrap()
log r.ok r.code
log r.stdout.trim()
log r.stderr.trim()

auto good = process.run("echo", ["fine"]).unwrap()
log good.ok good.code
Click the code to edit it

Note the difference: exec returns the output text and fails on a bad exit; run returns a record and fails only when the program can't be started at all. Use run for programs like grep or diff, where a non-zero code carries meaning.

Try it: change the exit code to 0.

Options for run#

run takes options as a third argument:

  • cwd runs the command in another folder.
  • env adds environment variables.
  • input sends text to the program's standard input.
  • timeout stops a slow command (milliseconds).
  • check turns a non-zero exit into a RuntimeError.
  • maxOutput caps the bytes kept from each stream.
main.osl
import "std:process"
import "std:fs"

fs.remove("work", {force: true}).unwrap()
fs.mkdir("work").unwrap()
fs.writeFile("work/note.txt", "inside work").unwrap()

log process.run("cat", ["note.txt"], {cwd: "work"}).unwrap().stdout
auto reply = process.run("sh", ["-c", "echo $GREETING"], {env: {GREETING: "hi there"}})
log reply.unwrap().stdout.trim()

auto sorted = process.run("sort", [], {input: "pear\napple\nfig\n"}).unwrap()
log sorted.stdout.split("\n")
Click the code to edit it

Timeouts and checks#

A command that runs too long is killed, and run fails with TimeoutError. With check: true, a failing exit becomes an error too, so you can pass it up with !.

main.osl
import "std:process"

auto slow = process.run("sh", ["-c", "sleep 5"], {timeout: 100})
log slow.unwrapErr().kind

auto strict = process.run("sh", ["-c", "exit 4"], {check: true})
log strict.unwrapErr().kind
Click the code to edit it

Real-world: wrap a tool#

Put it together as a function that returns a result, so callers decide how to react:

main.osl
import "std:process"

def firstLine(string command, string[] args) result<string, error> (
  auto r = process.run(command, args)!
  if !r.ok return result.err(error(command ++ " exited with " ++ r.code))
  return result.ok(r.stdout.split("\n")[1])
)

log firstLine("printf", ["one\ntwo\n"]).unwrap()
log firstLine("sh", ["-c", "exit 1"]).unwrapErr().message
log firstLine("missing-tool-xyz", []).unwrapErr().kind
Click the code to edit it

Background processes#

process.start(command, args, options) starts a command and returns a Child handle at once. The program keeps running while yours does other work. wait() blocks until it finishes and gives the same record as run.

main.osl
import "std:process"

auto child = process.start("sh", ["-c", "echo working; sleep 0.2; echo done"]).unwrap()
log "started, pid positive:" (child.pid() > 0)
log "still running:" child.isRunning()
auto done = child.wait()
log done.code
log done.stdout.split("\n")
log child.isRunning()
Click the code to edit it

Start several children, then wait for each, and they run at the same time:

main.osl
import "std:process"

*process.Child[] jobs = []
for name in ["a", "b", "c"] (
  jobs.append(process.start("sh", ["-c", "sleep 0.2; echo job " ++ name]).unwrap())
)
for job in jobs (
  log job.wait().stdout.trim()
)
Click the code to edit it

Stopping a process#

child.kill() stops it at once, and child.signal("TERM") asks politely ("INT", "TERM", "KILL", "HUP" and "QUIT" are accepted). A killed process has exit code -1. close() kills the child if it is still running and waits for it, which makes it a good last line of cleanup.

main.osl
import "std:process"

auto child = process.start("sh", ["-c", "sleep 30"]).unwrap()
log child.isRunning()
child.signal("TERM").unwrap()
auto r = child.wait()
log r.ok r.code
log child.signal("TERM").unwrapErr().kind
child.close()
Click the code to edit it

The current program#

A few functions describe the program itself. process.args() gives the command-line arguments after the program name (none here, because the examples start without any), process.cwd() the working directory, and process.chdir(path) changes it for the whole program.

main.osl
import "std:process"
import "std:fs"

log process.args().len

fs.remove("project", {force: true}).unwrap()
fs.mkdir("project").unwrap()

string start = process.cwd().unwrap()
process.chdir("project").unwrap()
log fs.base(process.cwd().unwrap())
process.chdir(start).unwrap()
log process.chdir("nowhere-at-all").unwrapErr().kind
Click the code to edit it

Process IDs identify running programs. They differ on every run, so print facts about them rather than the numbers:

main.osl
import "std:process"

log process.pid() > 0
log process.ppid() > 0
log process.isRunning(process.pid())
log process.cpuTime() >= 0
log process.executable().unwrap().len > 0
Click the code to edit it

Listing processes#

process.list() describes every running process with its pid, ppid, user, command, memory and CPU use. It runs ps, so it fails with NotFoundError where ps doesn't exist. The list differs on every machine, so find what you need by filtering. This finds the current program:

main.osl
import "std:process"

auto me = process.pid()
for p in process.list().unwrap() (
  if p.pid == me (
    log "found myself:" p.command
  )
)
Click the code to edit it

Exit codes#

process.exit(code) ends the program at once with a status. 0 means success and anything else means failure, which shells and CI systems check. Deferred code doesn't run, so clean up first.

main.osl
import "std:process"

log "checking..."
bool ok = false
if !ok (
  log "check failed"
  process.exit(3)
)
log "not reached"
Click the code to edit it
Quick check

What is the difference between process.exec and process.run?

Your turn

Count words with wc

Run sh -c "echo one two three | wc -w" with exec and print the number it reports, converted to an int, plus one. If the command fails, print unavailable.

main.osl
Ctrl Enter runs
Expected output
4
Your turn

Report the exit code

Run sh -c "exit 7" with run. If it didn't succeed, print failed with 7 using the real code.

main.osl
Ctrl Enter runs
Expected output
failed with 7

API reference#

61 members, generated from the std:process source of OSL 9.3.0.

Functions

#process.args() string[]

args returns the arguments the program was started with, without the program itself.

#process.background(any command, any ...args) *process.Process

Deprecated. Deprecated: use process.start(command, args) instead.

background starts a command and returns its handle, or null.

#process.chdir(string path) result<unit, error>

chdir changes the working directory, which relative paths resolve against, for the whole program. Fails with NotFoundError when path doesn't exist and IOError when it isn't a directory.

#process.cpuTime() number

cpuTime returns the CPU time the program has used so far, user and system combined, in milliseconds.

#process.cwd() result<string, error>

cwd returns the working directory. Fails with NotFoundError when it has been deleted.

#process.daemonize(any command, any ...args) boolean

Deprecated. Deprecated: use process.start(command, args) and process.exit(0).

daemonize starts a command and then exits the current program.

#process.environment() string[string]

Deprecated. Deprecated: use env.all() instead.

environment returns every environment variable.

#process.exec(string command, any ...args) result<string, error>

exec runs a command with arguments and returns its stdout. Unlike process.run it fails when the command exits with a non-zero code (RuntimeError, with the command's stderr in the message) or prints more than 16 MiB (IOError). Fails with NotFoundError when the command doesn't exist.

#process.execAsUser(any user, any command, any ...args) string

Deprecated. Deprecated: use process.run("sudo", ["-u", user, command, ...]) instead.

execAsUser runs a command through sudo -u user and returns its output, or "Error: …".

#process.executable() result<string, error>

executable returns the path of the running program. Fails with IOError when the OS can't report it.

#process.exit(int code)

exit ends the program immediately with an exit code; deferred blocks don't run.

#process.findByName(any name) object[]

Deprecated. Deprecated: use process.list() and filter by command instead.

findByName returns the ps aux rows whose command contains name.

#process.findByPID(any pid) object

Deprecated. Deprecated: use process.list() and filter by pid instead.

findByPID returns the ps aux row for a process ID, or null.

#process.fork() result<unit, error>

Deprecated. Deprecated: use process.start(process.executable(), process.args()) instead.

fork is not supported: Go programs can't fork. It always fails with UnsupportedError.

#process.getArg(any index) string

Deprecated. Deprecated: use process.args()[index] instead.

getArg returns argument index (1 is the program itself), or "".

#process.getArguments() string[]

Deprecated. Deprecated: use process.args() instead, which leaves out the program.

getArguments returns the program's arguments, starting with the program.

#process.getCPUTime() number

Deprecated. Deprecated: use process.cpuTime() instead, which is in milliseconds.

getCPUTime returns CPU time used, in seconds.

#process.getEnvironment(any key) string

Deprecated. Deprecated: use env.get(key) instead.

getEnvironment returns an environment variable, or "".

#process.getExecutablePath() string

Deprecated. Deprecated: use process.executable() instead.

getExecutablePath returns the program's path, or "".

#process.getExitCode() int

Deprecated. Deprecated: there is no replacement; use process.exit(code) to set it.

getExitCode is not supported: a program can't read its own exit code before it exits. It always throws UnsupportedError.

#process.getMemoryMB() number

Deprecated. Deprecated: use mem.stats().sys instead, which is in bytes.

getMemoryMB returns memory obtained from the OS, in megabytes.

#process.getNumCPU() int

Deprecated. Deprecated: use sys.cpus() instead.

getNumCPU returns the number of CPUs.

#process.getNumGoroutines() int

Deprecated. Deprecated: use mem.stats().goroutines instead.

getNumGoroutines returns the number of goroutines.

#process.getPID() int

Deprecated. Deprecated: use process.pid() instead.

getPID returns the current process ID.

#process.getPPID() int

Deprecated. Deprecated: use process.ppid() instead.

getPPID returns the parent process ID.

#process.isPIDRunning(any pid) boolean

Deprecated. Deprecated: use process.isRunning(pid) instead.

isPIDRunning reports whether a process the program may signal exists.

#process.isRunning(int pid) boolean

isRunning reports whether a process with the given ID exists, including processes owned by other users.

#process.kill(int pid) result<unit, error>

kill stops the process with the given ID immediately (SIGKILL). Fails with NotFoundError when no such process exists and PermissionError when it belongs to another user. Throws ValueError for a pid <= 0.

#process.killByName(any name) int

Deprecated. Deprecated: use process.list() and process.kill(pid) instead.

killByName kills every process whose command contains name and returns how many it killed.

#process.killPID(any pid) boolean

Deprecated. Deprecated: use process.kill(pid) instead.

killPID kills a process by ID and reports whether it worked.

#process.list() result<{command: string, cpu: number, mem: number, pid: int, ppid: int, rss: string (deprecated), rssBytes: int, start: string, startTime: int, stat: string, time: string, user: string, vsz: string (deprecated), vszBytes: int}[], error>

list describes every running process, using ps (macOS, Linux and other Unix systems). Each object has pid, ppid, user, cpu and mem (percent of CPU and memory), rssBytes and vszBytes (resident and virtual memory in bytes), stat, start (when it started, as ps aux prints it: "15:04" today, "Oct06" this year, else the year), startTime (when it started, in unix milliseconds), time (CPU time as ps prints it) and command. The deprecated keys rss and vsz are the memory sizes as kilobyte strings, as before. Fails with NotFoundError when ps isn't available.

#process.pid() int

pid returns the current program's process ID.

#process.pipe(*process.Process process1, *process.Process process2) boolean

Deprecated. Deprecated: use process.run(command, args, {input: text}) or run a pipeline with process.run("sh", ["-c", "a | b"]).

pipe connects one spawned command's stdout to another's stdin.

#process.ppid() int

ppid returns the process ID of the program that started this one.

#process.run(string command, array args, object? options) result<{code: int, ok: boolean, stderr: string, stdout: string, truncated: boolean}, error>

run runs a command to completion and returns how it finished: an object with ok (exit code 0), code (the exit code, -1 when killed by a signal), stdout, stderr and truncated (output went past maxOutput). A non-zero exit is not an error unless check is set. args are passed to the command directly, with no shell. Options: timeout (milliseconds, 0 for none), maxOutput (bytes kept per stream, default 16 MiB), cwd, env (variables added to the current environment), input (text sent to stdin) and check (fail with RuntimeError on a non-zero exit). Fails with NotFoundError when the command doesn't exist, PermissionError when it can't be run and TimeoutError when the timeout passes (the process is killed).

#process.setEnvironment(any key, any value) boolean

Deprecated. Deprecated: use env.set(key, value) instead.

setEnvironment sets an environment variable and reports whether it worked.

#process.setNumCPU(any n)

Deprecated. Deprecated: there is no replacement; the runtime picks this from sys.cpus().

setNumCPU limits how many CPUs run OSL code at once.

#process.setWorkingDir(any path) boolean

Deprecated. Deprecated: use process.chdir(path) instead.

setWorkingDir changes the working directory and reports whether it worked.

#process.signal(int pid, string name) result<unit, error>

signal sends a signal (see Child.signal for names) to the process with the given ID. Fails like process.kill; throws ValueError for an unknown signal name or a pid <= 0.

#process.signalPID(any pid, any sig) boolean

Deprecated. Deprecated: use process.signal(pid, name) instead.

signalPID signals a process by ID and reports whether it worked.

#process.spawn(any command, any ...args) *process.Process

Deprecated. Deprecated: use process.run(command, args, options?) to run a command, or process.start(command, args, options?) to start one in the background.

spawn makes a command handle without starting it.

#process.spawnShell(any command) *process.Process

Deprecated. Deprecated: use process.run("sh", ["-c", command]) instead.

spawnShell makes a handle for a shell command line without starting it.

#process.start(string command, array args, object? options) result<*process.Child, error>

start starts a command in the background and returns its Child handle; its output is collected for process.wait(). args are passed directly, with no shell. Options: maxOutput, cwd, env and input, as for process.run. Fails with NotFoundError when the command doesn't exist or PermissionError when it can't be run.

#process.waitPID(any pid) object

Deprecated. Deprecated: use process.start(command, args) and Child.wait() instead.

waitPID waits for a child process by ID and returns {success, pid, code} or {success, error}.

#process.workingDir() string

Deprecated. Deprecated: use process.cwd() instead.

workingDir returns the working directory, or "".

*process.Child

#child.close()

close kills the process if it is still running and waits for it to exit. Closing again does nothing.

#child.isRunning() boolean

isRunning reports whether the process has not exited yet.

#child.kill() result<unit, error>

kill stops the process immediately (SIGKILL). Fails with StateError when it has already exited.

#child.pid() int

pid returns the process ID.

#child.signal(string name) result<unit, error>

signal sends a signal to the process: "INT", "TERM", "KILL", "HUP" or "QUIT" (a "SIG" prefix is allowed). Throws ValueError for any other name; fails with StateError when the process has already exited.

#child.wait() {code: int, ok: boolean, stderr: string, stdout: string, truncated: boolean}

wait blocks until the process exits and returns how it finished, with the same keys as process.run: ok, code, stdout, stderr and truncated. Calling it again returns the same object.

*process.Process

#process.getPID() int

Deprecated. Deprecated: use process.start(command, args) and Child.pid() instead.

getPID returns a started command's process ID, or 0.

#process.isRunning() boolean

Deprecated. Deprecated: use process.start(command, args) and Child.isRunning() instead.

isRunning reports whether a started command is still running.

#process.kill() boolean

Deprecated. Deprecated: use process.start(command, args) and Child.kill() instead.

kill kills a started command and reports whether it worked.

#process.run() object

Deprecated. Deprecated: use process.run(command, args, options?) instead.

run runs the command and returns {success, output, error, code, timeout, truncated}.

#process.runLimited(any maxBytes) object

Deprecated. Deprecated: use process.run(command, args, {maxOutput: bytes}) instead.

runLimited runs the command keeping at most maxBytes of output.

#process.runTimeout(any timeout) object

Deprecated. Deprecated: use process.run(command, args, {timeout: milliseconds}) instead.

runTimeout runs the command with a timeout in seconds.

#process.runTimeoutLimited(any timeout, any maxBytes) object

Deprecated. Deprecated: use process.run(command, args, {timeout: milliseconds, maxOutput: bytes}) instead.

runTimeoutLimited runs the command with a timeout in seconds, keeping at most maxBytes of output.

#process.signal(any sig) boolean

Deprecated. Deprecated: use process.start(command, args) and Child.signal(name) instead.

signal signals a started command and reports whether it worked.

#process.start() boolean

Deprecated. Deprecated: use process.start(command, args, options?) instead.

start starts the command and reports whether it started.

#process.wait() object

Deprecated. Deprecated: use process.start(command, args) and Child.wait() instead.

wait waits for a started command and returns {success, output, error, code}.