Skip to content
ALB Wiki

Shell

This is an old revision of this page — revision 1 (2026-10-25 14:48:41 UTC). It may differ from the current revision.

The shell is the operating system's front door — the program that reads your commands, expands them into what the kernel executes, and returns what happened. Everything else in this handbook runs through it. This chapter teaches the POSIX shell grammar — the portable core every sh speaks — with bash's extensions flagged where they genuinely earn their keep. Write POSIX, reach for bash deliberately: that's the discipline.

Which shell is which

echo $SHELL            # your login shell
ls -l /bin/sh          # WHAT sh actually is on this system — varies!

/bin/sh is a symlink: to bash on Slackware, dash on Debian, bash in POSIX mode elsewhere. The distinction matters — scripts shebang'd #!/bin/sh must be portable (no bashisms), while #!/bin/bash buys the extensions. dash exists specifically to be the fast, strict POSIX sh that doesn't forgive sloppy scripts.

The grammar — the eight expansions

When you hit enter, the shell transforms your line in a fixed order — learn it once:

echo ~ $HOME $((1+2)) $(uname) *.txt {a,b} "quoted" 'literal'
# 1 brace  2 tilde  3 param+arith+cmdsubst  4 word-split  5 glob  6 quote-removal

Each stage is a tool and a trap:

  • $var / ${var} — parameter expansion; ${var:-default}, ${var#prefix}, ${var%suffix}, ${#var} string ops are POSIX
  • $(cmd) — command substitution, nestable, POSIX; backticks are the legacy form
  • $((expr)) — arithmetic, integer only
  • *.txt — globbing: * any run, ? one char, [abc]/[a-z] classes, [!a] negation
  • {a,b,c} — brace expansion — bash only, not POSIX
  • Tilde — ~, ~user
  • Quotes — '...' fully literal; "..." protects spaces but allows $; "$@" is the special case

Quoting — where every shell bug lives

f="my file.txt"
ls $f          # TWO words — word splitting — the classic bug
ls "$f"        # one word — quote variables ALWAYS
echo '$HOME'   # literal $HOME — single quotes stop everything
echo "$HOME"   # expands

The rule that never lies: quote every variable expansion unless you specifically want word-splitting. rm -rf $dir unquoted is how systems die; rm -rf "$dir" survives spaces.

Redirection — the complete grammar

cmd > out          # stdout to file (truncate)
cmd >> out         # append
cmd 2> err         # stderr
cmd > out 2>&1     # both to one file — order matters!
cmd &> out         # bash shorthand for both
cmd < in           # stdin from file
cmd << EOF         # heredoc — literal input until EOF
cmd <<< "str"      # herestring — bash only
cmd | next         # stdout → stdin — the pipe
cmd1 && cmd2       # run second only if first succeeds
cmd1 || cmd2       # run second only if first fails
cmd ; next         # sequence
cmd &              # background

2>&1 ordering is the subtle one — > out 2>&1 binds stderr to stdout's destination; 2>&1 > out binds stderr to the old stdout (terminal). Order is everything.

Exit codes — the contract

Every command returns 0–255: 0 = success, nonzero = failure classes. $? reads the last one:

grep -q match file && echo found
[ -f file ] && echo exists        # test — the conditional primitive
test -d dir || mkdir dir

The conditional builtins: [/test (POSIX) — -f/-d/-r/-w/-x, -z/-n, =/!=, -eq/-lt/gt. bash's [[ ]] adds pattern matching and &&/|| inside — not POSIX but genuinely better.

Job control

cmd &              # background it
jobs               # list
fg %1              # foreground job 1
bg %1              # resume in background
Ctrl-Z             # suspend
Ctrl-C             # SIGINT — interrupt
Ctrl-D             # EOF — end stdin
nohup cmd &        # survive hangup

Special shell powers

  • Builtins vs binaries — type cd echo test reveals which live in the shell (cd, echo, [, export) vs which are /bin programs; type -a shows all resolutions. Builtins run in-shell — cd must be a builtin to change your process's cwd.
  • exec — replaces the shell process; exec program never returns; exec > log redirects the shell itself
  • set — the shell's own switches: set -e die on error, set -u die on unset vars, set -x trace, set -o pipefail (bash) — the script-safety preamble
  • hash — bash caches binary locations; hash -r clears it (the LFS set +h trick)
  • trap — run code on signals: trap 'cleanup' EXIT
  • eval — re-parse a string as shell; powerful and dangerous — quote-sanitized input only

Environment and startup

export VAR=val     # into the environment — children inherit
env                # dump it
env -i cmd         # the sterile env — LFS's tool

Startup files differ by invocation: login shells read /etc/profile + ~/.profile (POSIX) — bash adds ~/.bash_profile; interactive non-login read ~/.bashrc; non-interactive (scripts) read neither — which is why a variable set in .bashrc doesn't exist inside a script. su - user gives a login shell; su user doesn't — the - matters.

POSIX vs bash — the honest table

FeaturePOSIXbash
$(cmd), >, |, &&✓✓
[[ ]], <<<, &>✗✓
{a,b} braces✗✓
Arrays arr=(...)✗✓
local, declare✗✓
${var/pat/rep}, regex =~✗✓
pipefail, BASH_SOURCE✗✓

Write POSIX for portability; use bash deliberately for arrays/[[/regex — and say so in the shebang.

Where shells go wrong

SymptomCause
Variable empty in script, set in .bashrcStartup files don't run non-interactively
command not found right after installbash hash cache — hash -r or set +h
rm: no such file on a file that existsUnquoted var word-split on spaces
2>&1 captures nothingOrder wrong — > file 2>&1 not 2>&1 > file
Script works in bash, fails in shBashisms — [[, arrays, <<< in a #!/bin/sh script
Glob didn't expandQuoted it — "*.txt" is literal
Ctrl-C doesn't kill itSIGINT ignored/trapped — try kill -9

Exercises

  1. Predict, then check: echo $(echo $(echo deep)) — nesting.
  2. ls *.{c,h} vs ls "*.txt" — which is literal?
  3. false && echo no || echo yes — trace the logic.
  4. set -x; ls nonexistent; set +x — watch the trace.
  5. Write a one-liner proving cd must be a builtin.
  6. env -i bash -c 'env' — what's in a sterile env?

See also

Categories: Handbook · Linux · Linux Handbook · Shells
This page was last edited 2026-10-25 14:48:41 UTC by ALB Team. What links here