# 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