Bash Scripting
Variables, conditionals, loops, functions, and arrays — through to error handling, traps, and debugging.
[[ ]], ${VAR^^}) is a bash extension, not portable to a plain POSIX
/bin/sh.
name="Ada"
echo "Hello, $name" # Hello, Ada
echo "Hello, ${name}!" # braces needed when concatenating right up against text
No spaces are allowed around the = in an assignment — name = "Ada" is
parsed as running a command called name with arguments = and "Ada",
not an assignment, and fails.
if [[ -f "$file" ]]; then echo "exists" elif [[ -d "$file" ]]; then echo "it's a directory" else echo "not found" fi
[[ ]] is a bash keyword (not portable to plain sh) and is the one to reach
for in bash scripts: unlike [ ]/test, unquoted variables inside it don't
undergo word-splitting or globbing, and it supports &&/|| directly
inside the brackets and == pattern matching.
[[ cond1 && cond2 ]] / [[ cond1 || cond2 ]]
logical and/or, usable directly inside a single [[ ]]
select choice in "cat" "dog" "bird"; do echo "you picked $choice" break done
select builds a numbered menu from a list, prompts for a number, and puts the
corresponding item in the loop variable — a quick way to get an interactive menu without
hand-rolling one out of read and a case. Like other loops, it repeats until
break (or the user sends EOF).
case "$1" in
start|up)
echo "starting"
;;
stop|down)
echo "stopping"
;;
*)
echo "usage: $0 {start|stop}"
exit 1
;;
esac
Each pattern branch ends with ;;; patterns support the same glob syntax as filename
matching, and | separates alternatives for a single branch. *) is the
conventional catch-all default, matching anything not matched above it.
greet() {
local name="$1"
echo "Hello, $name"
}
greet "Ada"
result=$(greet "Ada") # capture the printed output instead of letting it print
fruits=("apple" "banana" "cherry")
fruits+=("date") # append
echo "${fruits[0]}" # apple
echo "${fruits[@]}" # every element, each its own word
echo "${#fruits[@]}" # 4 -- the number of elements
for f in "${fruits[@]}"; do
echo "$f"
done
echo "scale=3; 10/3" | bc
bash's $(( )) can't do floating-point math; piping an expression to bc (a separate calculator program) is the standard workaround, with scale setting the decimal precision
cmd1 | cmd2
pipes cmd1's stdout into cmd2's stdin
cat <<EOF Multi-line text with $variable expansion. EOF cat <<'EOF' Literal text -- $this is NOT expanded here. EOF grep "$pattern" <<< "$single_line_string"A here-document (
<<DELIM ... DELIM) feeds multi-line text to a command's stdin; quoting
the delimiter (<<'EOF') disables variable expansion inside it. A here-string
(<<<) does the same for a single expression, without needing a whole block.
cmd1 || cmd2
runs cmd2 only if cmd1 failed
set -e
(errexit) exits the script immediately if any command fails. Doesn't trigger inside an if/while condition, inside all-but-the-last part of an &&/|| chain, or inside a function called from one of those — treat it as a safety net, not a guarantee
set -o pipefail
makes a pipeline's exit status the last NONZERO status among all its commands, instead of just the final command's — without this, cmd_that_fails | cmd_that_succeeds reports success
find . -print0 | xargs -0 cmd
builds and runs a command line from a list of filenames read from stdin. The -print0/-0 pairing uses NUL as a separator instead of newlines, the only safe way to handle filenames containing spaces or even newlines
while getopts "vf:" opt; do
case "$opt" in
v) verbose=1 ;;
f) file="$OPTARG" ;;
*) echo "usage: $0 [-v] [-f file]"; exit 1 ;;
esac
done
getopts is the standard way to parse -v-style flags and -f value-style
options in a script, instead of hand-rolling argument parsing with a series of if/case
checks against "$@".