# process

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

This is the Markdown version of https://osl-lang.org/packages/process. Every page of the OSL docs has one: add .md to its path. https://osl-lang.org/llms.txt lists them all.

```osl
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](/learn/errors.md). Arguments are passed straight to the program, without a shell.

```osl
import "std:process"

string out = process.exec("echo", "hello", "from", "echo").unwrap()
log out.trim()
log out.len
```

Output:

```text
hello from echo
16
```

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.

```osl
import "std:process"

log process.exec("echo", "$HOME", "*", "a|b").unwrap().trim()
```

Output:

```text
$HOME * a|b
```

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`.

```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
```

Output:

```text
RuntimeError : process.exec: sh exited with code 3: oops
NotFoundError
```

## 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.

```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
```

Output:

```text
false 2
out
err
true 0
```

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.

```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")
```

Output:

```text
inside work
hi there
["apple","fig","pear",""]
```

## 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 `!`.

```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
```

Output:

```text
TimeoutError
RuntimeError
```

## Real-world: wrap a tool

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

```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
```

Output:

```text
one
sh exited with 1
NotFoundError
```

## 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`.

```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()
```

Output:

```text
started, pid positive: true
still running: true
0
["working","done",""]
false
```

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

```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()
)
```

Output:

```text
job a
job b
job c
```

## 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.

```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()
```

Output:

```text
true
false -1
StateError
```

## 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.

```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
```

Output:

```text
0
project
NotFoundError
```

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

```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
```

Output:

```text
true
true
true
true
true
```

## 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:

```osl
import "std:process"

auto me = process.pid()
for p in process.list().unwrap() (
  if p.pid == me (
    log "found myself:" p.command
  )
)
```

Its output changes from run to run.

## 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.

```osl
import "std:process"

log "checking..."
bool ok = false
if !ok (
  log "check failed"
  process.exit(3)
)
log "not reached"
```

Output:

```text
checking...
check failed
```

**Quiz.** What is the difference between `process.exec` and `process.run`?

- [x] `exec` returns the output and fails on a non-zero exit. `run` returns a record and only fails if the command can't start.
- [ ] `run` uses a shell and `exec` doesn't.
- [ ] There is none.

Answer: Neither uses a shell. Use `run` when the exit code carries meaning, as with `grep`.

### Exercise: 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`.

Starting code:

```osl
import "std:process"

log process.exec("echo", "unchanged").unwrap().trim()
```

It should print:

```text
4
```

Hint 1: `exec("sh", "-c", "echo one two three | wc -w")` gives text such as `3\n`.

Hint 2: `.trim().tryAs<int>.unwrapOr(0)` converts safely. Use `switch` on the exec result to handle failure.

A solution:

```osl
import "std:process"

switch process.exec("sh", "-c", "echo one two three | wc -w") (
  case ok(out)
    log out.trim().tryAs<int>.unwrapOr(0) + 1
  case err(e)
    log "unavailable"
)
```

### Exercise: Report the exit code

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

Starting code:

```osl
import "std:process"

log process.run("echo", ["hi"]).unwrap().ok
```

It should print:

```text
failed with 7
```

Hint 1: `process.run("sh", ["-c", "exit 7"]).unwrap()`

Hint 2: Check `r.ok`, print `r.code`.

A solution:

```osl
import "std:process"

auto r = process.run("sh", ["-c", "exit 7"]).unwrap()
if !r.ok ( log "failed with" r.code )
```

## API reference

## API reference

Every member of `std:process`, generated from the source of OSL.

### 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}.
