# OS The `os/` namespace covers process identity and environment, subprocess execution, and OS signal handling. It is compiled into every runtime and needs no import. Commands run either directly with an explicit argument vector (`os/run`, `os/status`, `os/lines`) or through `/bin/sh -c` (`os/shell`); both forms capture stdout and stderr and report a status. Sandboxed sessions may be denied subprocess or network access — see Sandbox. ## Functions ### Process and environment | Function | Purpose | |---|---| | `os/args` | Command-line arguments as a vector of strings; `argv[0]` is the script path | | `os/cwd` | Current working directory | | `os/chdir "path"` | Change the process working directory; returns `nil`, or `%Err` on failure | | `os/env "NAME"` | Environment variable value as a string, or `nil` when unset | | `os/pid` | Current process id | | `os/cpu-count` | Logical CPU count (at least 1); used to size worker pools | | `os/exit ?code` | Terminate with an exit code (`0`–`255`, default `0`); never returns | ### Subprocesses | Function | Purpose | |---|---| | `os/run cmd args...` | Run a command directly and capture its result map | | `os/run-async cmd args...` | Run the same command off-thread; returns a Future of the result map | | `os/shell "command string"` | Run a string through `/bin/sh -c` — pipes, redirection, globbing | | `os/status cmd args...` | Run a command; returns the result map (see note) | | `os/lines cmd args...` | Run a command and return stdout split into a vector of lines | ### Signals | Function | Purpose | |---|---| | `os/on-signal $signal $handler` | Register a handler closure for a signal; `true` or `%Err` | | `os/off-signal $signal` | Remove every handler for a signal; returns the number removed | | `os/signals` | Vector of the catchable signal keywords | | `os/kill $pid $signal` | Send a signal by name to a process id; `true` or `%Err` | Notes: - Every subprocess call yields a result map of the shape `%{ :status N :stdout "..." :stderr "..." }`. Output is captured (up to 10 MiB per stream) and trailing newlines are trimmed. `:status` is the exit code; a signal-terminated child maps to `128`, a stopped one to `127`. - `os/run`, `os/status`, and `os/lines` take an executable plus its arguments directly — no shell interpretation. Use `os/shell` when you need a pipeline, or the `[|cmd]` syntax, which is the convenience form of the same thing and returns only stdout (or `%Err` on a non-zero exit). Non-string arguments are formatted for the command line; at most 64 arguments are used. - `os/run-async` resolves to the same `%{ :status :stdout :stderr }` map behind a Future: `[await [os/run-async ...]]`. A missing executable or a spawn failure resolves to `%Err{:message}`; a missing command name or a failed executor returns `%Err` immediately (not as a Future). A hung command pins a pool thread, so prefer `os/run` for simple blocking use. - `os/status` returns the same `%{ :status :stdout :stderr }` map as `os/run`; read `$res~status` when you only need the exit code. - Signals are catchable for `:sigint`, `:sigterm`, `:sighup`, `:sigquit`, `:sigusr1`, `:sigusr2`, and `:sigalrm`. Names are keywords or strings, case-insensitive, and may carry an optional `sig` prefix (`:sigusr1`, `"USR1"`, `:usr1`). `sigkill` and `sigstop` are send-only — the kernel never delivers them to a handler. - Handlers fire only while the event loop pumps (`vwait`, `update`, REPL, servers). Registering a signal intercepts it, so Ctrl-C no longer kills while a handler exists. The handler receives `%{ :type :signal :signal :code :pid :uid }`; `:pid`/`:uid` identify the sender. Use `os/off-signal` to unblock the signal and restore its default disposition. A registered handler keeps `vwait` alive, so "run until signalled" is the natural shape. ## Examples ### Run a command and inspect the result
set res [os/run "uname" "-s"]
echo "status:" $res~status
echo "stdout:" $res~stdout
if ($res~status != 0) {
    echo "stderr:" $res~stderr
}
### Shell pipelines and line output
set res [os/shell "ps aux | grep deft | wc -l"]
echo "deft processes:" $res~stdout

each [os/lines "printf" "a\nb\nc"] {|line| echo "got:" $line }
### Off-thread execution
def fut [os/run-async "sh" "-c" "echo out; echo err >&2"]
set res [await $fut]
# %{ :status 0 :stdout "out" :stderr "err" }
### Signal handling
[os/on-signal :sigusr1 {|ev|
    echo "got" $ev~signal "from pid" $ev~pid
}]
[os/kill [os/pid] :sigusr1]     # self-send for testing
[update]                        # pump the event loop once -> handler fires
echo "removed:" [os/off-signal :sigusr1]
### Environment and arguments
echo "cwd:" [os/cwd] "pid:" [os/pid] "cpus:" [os/cpu-count]

set home [os/env "HOME"]
if [nil? $home] { echo "HOME is not set" }

each [os/args] {|arg| echo $arg }
## See Also - I/O — channels and the bare `open` / `puts` / `gets` / `close` verbs - Sandbox — Landlock restrictions on subprocesses and network - Tasks — `await` and the Future consumer verbs - Filesystem — `fs/*` file and path helpers