The TonicBoxOS command shell: a small POSIX-style sh with pipes, redirection, variables and
expansions, command and arithmetic substitution, globbing, control flow, functions, aliases, and job
control. It runs interactively in the terminal, executes scripts, and is the login shell that sources
your profile.
ls | grep errx=5; echo $xfor f in *.c; do ...cmd > out.txtalias ll='ls -lp'Ctrl-Z → fg
With no arguments sh is an interactive read-eval-print loop. It draws its own prompt -
user@dev:~/path$ in green, or root@dev:...# in red when you are root - and
~ abbreviates your $HOME. It also runs non-interactively:
| Form | Runs |
|---|---|
sh SCRIPT [args] | a script file; $0 is the script, $1… the arguments |
sh -c 'CMDS' [name [args]] | a command string, with optional positional parameters |
#!/bin/sh | a shebang script, run through the shell by the kernel |
Started as a login shell (argv[0] beginning with -), it first sources
/etc/profile and then ~/.profile - that is where the default aliases and
environment live.
Input can span several lines. If a line ends with a backslash (line continuation), inside an
open quote, on a trailing |, && or ||, inside an
unclosed if / while / for / case /
{ block, or right after a bare function head, the shell keeps reading until the
command is complete.
These run inside the shell itself rather than as separate programs:
| Builtin | Effect |
|---|---|
cd [DIR] | change directory; no argument goes to your home, - to the previous directory |
exit [N] | leave the shell with status N |
break [N] / continue [N] | exit or restart the enclosing loop (N levels) |
return [N] | return from a function with status N |
shift [N] | drop the first N positional parameters (default 1) |
set [--] ARGS | set the positional parameters |
export NAME[=VAL] | put a variable into the environment for child programs |
unset NAME | remove a variable |
read VAR | read one line of input into VAR (status 1 at end of input) |
test EXPR / [ EXPR ] | evaluate a condition (see below) |
true / false / : | do nothing, with status 0 / 1 / 0 |
. / source FILE | run a file's commands in the current shell |
alias / unalias | define, list, or remove aliases |
umask [OCT] | show or set the octal file-creation mask |
jobs / fg / bg | job control (see below) |
Anything else is resolved as an alias, then a builtin, then a function, then an external program (found via
execvp on $PATH). test / [ understand
-z / -n (empty / non-empty string), -e / -f /
-d (path exists / is a file / is a directory), -r / -w / -x / -s
(readable / writable / executable / non-empty), string = and !=,
the integer comparisons -eq -ne -lt -le -gt -ge, ! EXPR (negate),
-a / -o (and / or), and a bare STRING (true when non-empty).
NAME=VALUE (no spaces around =) sets a shell variable; $NAME or
${NAME} expands it, and an undefined name expands to nothing. Shell-local variables shadow
the environment; export pushes one into the environment so child programs inherit it, and an
exported name is kept in sync.
| Parameter | Expands to |
|---|---|
$? | exit status of the last command |
$0 … $9 | positional parameters (script name / arguments) |
$# | number of positional parameters |
$@ / $* | all positional parameters, space-separated |
$$ / $! | shell PID / PID of the last background command |
$ name=world $ echo "hello, $name (last status $?)" hello, world (last status 0)
Quoting. 'single quotes' are fully literal - nothing inside expands,
not even a backslash. "double quotes" keep spaces as one word but still expand
$; inside them a backslash escapes only $, \,
", `, and a newline. Outside quotes a backslash escapes the next
character - \ is a literal space, \$ a literal dollar - and a
backslash at the end of a line continues the command onto the next line.
| Expansion | Does |
|---|---|
$(command) / `command` | substitutes the command's output (trailing newlines trimmed); may contain pipes and functions |
$(( expr )) | integer arithmetic (arithmetic operator set as in Limits) |
${NAME:-w} / := / :+ | default, assign-default, or alternate value |
${#NAME} | length of the value |
${NAME#pat} / ## / % / %% | strip a matching prefix or suffix |
* / ? / [...] | glob: any run, a single character, or a character class |
$ echo "there are $(ls *.c | wc -l) sources" $ echo $(( (3 + 4) * 2 )) 14
A glob that matches nothing is left untouched, so the literal pattern is passed through.
| Operator | Effect |
|---|---|
a | b | pipe a's output into b; stages run concurrently |
a ; b | run a, then b |
a && b | run b only if a succeeded |
a || b | run b only if a failed |
cmd > f / >> f | send stdout to a file (truncate / append) |
cmd < f | read stdin from a file |
cmd 2> f | send stderr to a file; any descriptor works with N> f |
cmd > f 2>&1 | send stderr wherever stdout currently points; redirections apply left to right |
cmd &> f | send both stdout and stderr to a file |
cmd >&- / <> f | close a descriptor; open a file for read-write |
cmd & | run in the background |
Pipelines are real forked processes joined by pipes, so a reader that exits early terminates the writer -
yes | head stops cleanly. A background command prints [bg] PID and is announced
as [done] PID at the next prompt.
if test -f config; then echo found elif test -d config; then echo a directory else echo missing fi while read line; do echo "$line"; done < file for f in *.c; do echo "$f"; done case "$answer" in y|yes) echo ok ;; *) echo no ;; esac
A command is "true" when its exit status is 0. until is while with the condition
negated. case patterns are globs and may list alternatives separated by |; the
first matching branch runs.
A function groups commands under a name. Inside it, $1, $2… are the
arguments passed to the call and $# their count.
$ greet() { echo "hi, $1"; } $ greet world hi, world
An alias is a shortcut expanded before the command runs. alias name='value'
defines one, alias with no arguments lists them, and unalias name removes one.
Expansion is recursive but loop-safe - an alias can refer to a command of the same name without recursing
forever. The default aliases (ll, la, .., …) are defined in
/etc/profile.
Job control is active only when a real interactive terminal is attached (piped or headless input runs the shell as a plain non-interactive loop with no job control).
| Action | Effect |
|---|---|
cmd & | start a job in the background |
Ctrl-Z | suspend the foreground job ([N]+ Stopped) |
jobs | list active jobs |
fg [N] | resume a job in the foreground |
bg [N] | resume a suspended job in the background |
$(( )) supports + - * / % & | ^ << >> ~, the comparisons < > <= >= == !=, && / ||, unary -, parentheses, and hex/octal literals, but has no assignment operators; division or modulo by zero yields 0.*, ?, and [...] character classes are supported, and ~ / ~/path expand to your home. There is no brace {} expansion or ~user form.<, >, >>, 2>, any N>, 2>&1, &>, <>, >&-, << here-documents, and <<< here-strings - but there is no <(...) process substitution.while / until loops stop after 100000 iterations.