std.os
Process-level helpers that live directly on std (not under a submodule) —
system, exit, chdir, pid, and the arg_parser factory. Call them
with dot, like any module function. There is no std.os object — the std.os title is just this
page’s name (call std.system(...), not std.os.system(...)). See
Overview. For the working directory use
std.path.cwd() (the old std.cwd alias was removed).
Reference
std.system(command) # run a shell command -> {output, status}
std.exit([code]) # terminate the process
std.chdir(path) # change cwd (returns nil)
std.pid() # current process ID (long)
std.system — run a command and capture its stdout
std.system(command) runs the command through the shell and returns an
object with two fields:
output— a string of everything the command wrote to stdout.status— the process exit code (0on success, non-zero on
failure like1or127).
var r = std.system("whoami")
print(r.status) # 0 (success)
print(r.output) # cory (what the command printed)
var date = std.system("date +%Y-%m-%d")
print("today is", date.output) # today is 2026-08-31
# capture multi-line output as a single string (with embedded newlines)
var ls = std.system("ls /tmp")
print(ls.output) # one file path per line
Captured output typically ends with a trailing newline, so when you embed
it in another string you usually want to trim it:
var who = std.system("whoami").output
var name = std.string.trim(who) # drop the trailing newline
print("hello, " + name + "!")
Branch on the exit code:
var check = std.system("test -f /etc/hostname")
if (check.status == 0) {
print("file exists")
} else {
print("file missing")
}
print(std.system("exit 3").status) # 3 — the command's own exit code
std.system captures stdout; any stderr the command writes goes
straight to the script’s stderr and is not included in output. Redirect
inside the command if you need it, e.g. std.system("cmd 2>&1").
std.system(command) hands the whole string to the system shell (/bin/sh -c on POSIX, cmd /c on Windows) — it does not exec the program
directly. Shell metacharacters are live: ;, &&, |, $VAR, backquotes,
and globs all expand or chain. Never build the command from untrusted input
(user text, file names, network data) without sanitizing — that is a
command-injection hole:
# DANGEROUS if user_name comes from outside the script:
std.system("greet " + user_name) # user_name = "x; rm -rf ~" runs both
Pass only trusted, hardcoded commands, or strip/quote metacharacters
yourself. Guarded engines deny std.system entirely unless constructed with
allow_subprocess.
std.path.cwd and std.chdir — the working directory
std.path.cwd() returns the current directory as a string; std.chdir(path)
changes it (returning nil). They work together:
print(std.path.cwd()) # e.g. /home/user/projects
std.chdir("/tmp")
print(std.path.cwd()) # /tmp — the process cwd has changed
std.chdir("..") # relative paths work too
This matters because file operations and relative paths resolve against the
process working directory. std.chdir(path) is the only way to change the
directory.
See also std.path (std.path.cwd) and std.file.
std.pid — the process id
std.pid() returns the current process ID as a long (unique only among
currently-running processes):
print(std.pid()) # e.g. 95516
var tag = "proc-" + std.pid()
std.file.write("/tmp/" + tag + ".lock", "owned by " + std.pid())
print("announcing:", tag)
Run the same script twice and each writes its own file:
instance pid = 99716 my lock file: /tmp/proc-99716.lock
instance pid = 99735 my lock file: /tmp/proc-99735.lock
The pid is useful for prefixed log lines and for pid-named lock files.
std.arg_parser — command-line parsing
Scripts receive raw argv via std.env.args(), and std.arg_parser
turns it into a validated options object — flags, valued options, and
positionals with defaults, type coercion, and generated --help:
var p = std.arg_parser("greet", "greet someone, optionally loudly")
p.add_flag("loud", "l", "use capitals")
p.add_option("greeting", "g", "greeting word", "hello")
p.add_option("repeat", "n", "times to print", 1)
p.add_positional("name", "who to greet")
var args = p.parse(std.env.args())
# scrii_repl greet.scr --loud -n 3 Bob
# -> {loud: true, greeting: "hello", repeat: 3, name: "Bob", _help: false}
for (i : std.seq.range(args.repeat)) {
var line = args.greeting + ", " + args.name
if (args.loud) { line:upper() }
print(line)
}
| Builder | Purpose |
|---|---|
arg_parser([prog[, description]]) |
Create a parser (used in usage text) |
add_flag(name[, short[, description]]) |
Boolean --name / -s, plus --no-name to negate and --name=true/false |
add_option(name[, short[, description[, default[, choices]]]]) |
Valued --name v, --name=v, -s v, -sV; a numeric default coerces the value to its type; choices restricts it |
add_positional(name[, description[, default]]) |
Positional in declaration order; required unless a default is given |
parse(argv) |
Parse an array (usually std.env.args()) → {...values, _help} |
help() |
The generated usage text as a string |
Rules: combined shorts (-abc), -- ends option parsing (the rest is positional), unknown options / missing values / missing required or extra positionals throw INVALID_ARGUMENT naming the problem; a bad numeric coercion throws INVALID_CONVERSION. --help/-h print usage and return with _help set (check it, then std.exit(0) or stop) — unless you define your own help/h flag, which takes precedence.
Pure compute, so arg_parser stays available in guarded engines.