
Use the shell to connect small programs through arguments, files and exit statuses. This reference focuses on the points where scripts commonly change meaning: quoting, redirection, pipelines and tests. The examples use POSIX-style shell syntax unless a Bash feature is explicitly identified.
The shell running your terminal and the interpreter running a script can differ. sh script.sh selects sh; bash script.sh selects Bash. A shebang selects an interpreter when the operating system executes a file directly. Check the environment where the script will actually run, including its working directory and PATH.
Keep arguments intact
A command receives a list of arguments. Quoting helps preserve that list as data moves through variables. Try this self-contained example in sh or Bash:
show_args() {
printf 'count=%s\n' "$#"
for arg in "$@"; do
printf '<%s>\n' "$arg"
done
}
show_args 'two words' '*.txt' ''
Expected output:
count=3
<two words>
<*.txt>
<>
"$@" preserves each incoming argument, including an empty one. "$*" joins arguments into one word using the first character of IFS. Unquoted expansions can undergo field splitting and filename expansion. Those distinctions explain why a script may work with report.txt yet fail with annual report.txt. The POSIX rules for special parameters and expansion define the behavior.
Patterns to keep close at hand
| Task | Pattern | Interpretation |
|---|---|---|
| Print a value | printf '%s\n' "$value" |
Fixed format, data supplied separately |
| Test a readable file | [ -r "$file" ] |
Spaces around [ and ] are required |
| Supply file input | command < "$file" |
The shell opens the file for standard input |
| Replace output | command > "$out" |
Opens and truncates the destination before execution |
| Append output | command >> "$out" |
Opens the destination for append |
| Send a diagnostic | printf '%s\n' 'failed' >&2 |
Writes to standard error |
| Run after success | first && second |
Runs second if first returns zero |
| Run after failure | first || recover |
Runs recover if first returns nonzero |
Always consider when a destination is opened. Redirecting a failing command directly onto your only good report can leave an empty or partial report. For a replace-on-success workflow, generate a temporary file in the destination directory and promote it only after checking success. The SQLite report exercise provides a complete example with cleanup and explicit assumptions.
Separate output from status
Standard output carries results, standard error carries diagnostics, and the exit status tells the caller how execution ended. Zero conventionally indicates success. The meaning of other values belongs to the command: consult its manual before treating every nonzero value as the same event.
if output=$(printf '%s\n' 'ready'); then
printf 'result=%s\n' "$output"
else
printf '%s\n' 'Command failed' >&2
fi
This prints result=ready. Command substitution removes trailing newline characters, so it is unsuitable for preserving arbitrary file bytes. Keep binary data in files or streams.
A pipeline connects one command's output to the next command's input. In common default configurations, its status comes from the final command. A producer can fail while its consumer succeeds. Bash offers set -o pipefail; support and defaults vary across deployed shell versions. For a script that must run in older environments, test each important stage explicitly or document the interpreter and options it requires. The Bash pipeline documentation specifies Bash's behavior.
Read text without losing spaces
For a small line-oriented text file named names.txt, this loop preserves leading and trailing spaces and treats backslashes literally:
while IFS= read -r line || [ -n "$line" ]; do
printf '<%s>\n' "$line"
done < names.txt
The final condition processes an unterminated last line. This is a text-record pattern; it does not preserve newline terminators or support embedded NUL bytes. When parsing CSV, use a CSV parser or database importer because quoted fields can contain commas and line breaks.
Diagnose with a small fixture
First record the exact command, interpreter version, working directory and observed status. Reduce the input to two or three fabricated records. Test a space, an empty value and a failure. Run the interpreter's syntax check, then inspect execution with tracing only when the values are safe to display.
Keep expected output beside the fixture so future changes have a baseline. For a complete first script, use the Bash line-counting guide. For an inherited KornShell job, begin with the KornShell maintenance checklist to establish its actual runtime before changing syntax.