Exit Status and Special Variables
Every command you run in Bash returns an integer exit status captured in $?. A value of 0 means success; any thing else signals failure.
#!/usr/bin/env bash
# demo_exit.sh
whoami
echo "Last exit code: $?"
Save the snippet above as demo_exit.sh and run:
bash demo_exit.sh
You can use $? inside a script to verify whether previous commands succeeded:
#!/usr/bin/env bash
# check_steps.sh
cd /tmp
echo "cd returned: $?"
touch newfile.txt
echo "touch returned: $?"
./nonexistent
echo "nonexistent returned: $?"
Generating Random Strings
A common task is creating unpredictable passwords. Combine $RANDOM with md5sum and awk to get a compact, 32-character hash:
rand=$(printf '%s' "$RANDOM" | md5sum | awk '{print $1}')
echo "$rand"
String Slicing
Bash supports substring extraction with ${var:start:length}.
rand=$(printf '%s' "$RANDOM" | md5sum | awk '{print $1}')
# first 8 chars
echo "${rand:0:8}"
# last 8 chars
echo "${rand: -8}"
Default Values
Two idioms exist for supplying fallbacks:
# Use default only when variable is unset or empty
echo "Selected plan: ${1:-plan1}"
# Always override with default
echo "Legal age: ${1+18}"
Example script choose_plan.sh:
#!/usr/bin/env bash
echo "Your chosen plan is: ${1:-plan1}"
Test Expressions
Bash offers two equivalent syntxaes for testing:
# Bracket form
[ 1 -eq 1 ] && echo "true"
# test builtin
test 1 -eq 1 && echo "also true"
Exit codes: 0 means the expression evaluated to true; non-zero means false.
Logical Operators
# AND
[ -f report.txt ] && echo "report exists"
# OR
[ -d archive ] || mkdir archive
File Tests
| Flag | Meaning |
|---|---|
| -f | regular file |
| -d | directory |
| -x | executable |
[ -f config.yaml ] && echo "config.yaml is a file"
[ -d logs ] && echo "logs is a directory"
[ -x deploy.sh ] && echo "deploy.sh is executable"
Numeric Comparisons
#!/usr/bin/env bash
# expect_two.sh
[ "$#" -ne 2 ] && { echo "Usage: $0 arg1 arg2"; exit 1; }
echo "Received $1 and $2"
String Comparisons
#!/usr/bin/env bash
# compare_strings.sh
[ "$1" = "$2" ] && echo "Strings match" || echo "Strings differ"
Flow Control
Single if
#!/usr/bin/env bash
# single_if.sh
if [ "$1" = "male" ]; then
echo "Gender: male"
fi
Two-way if/else
#!/usr/bin/env bash
# two_way.sh
if [ "$1" = "male" ]; then
echo "Gender: male"
else
echo "Gender: female"
fi
Multi-way if/elif/else
#!/usr/bin/env bash
# multi_way.sh
if [ "$1" = "male" ]; then
echo "Gender: male"
elif [ "$1" = "female" ]; then
echo "Gender: female"
else
echo "Gender: unknown"
fi
case Statement
When many discrete values must be handled, case is clearer than nested ifs.
#!/usr/bin/env bash
# service.sh
case "$1" in
start)
echo "Starting service..."
;;
stop)
echo "Stopping service..."
;;
restart)
echo "Restarting service..."
;;
*)
echo "Usage: $0 {start|stop|restart}"
;;
esac
Loops
for Loop
#!/usr/bin/env bash
# list_files.sh
for f in /var/log/*.log; do
echo "Found log: $f"
done
while Loop
#!/usr/bin/env bash
# count.sh
counter=1
while [ "$counter" -le 5 ]; do
echo "$counter"
((counter++))
done
until Loop
#!/usr/bin/env bash
# countdown.sh
counter=1
until [ "$counter" -gt 5 ]; do
echo "$counter"
((counter++))
done
Arithmetic
a=1
let a+=1 # a=2
((a++)) # a=3
echo "$a"
Functions
#!/usr/bin/env bash
# greet.sh
greet() {
echo "Hello, $1"
}
greet "Alice"
greet "Bob"
Multiple positional parameters inside a function:
#!/usr/bin/env bash
# multi_param.sh
show() {
echo "First: $1"
echo "Second: $2"
echo "Third: $3"
}
show one two three
Functions reduce repetition and centralize logic, making scripts easier to maintain.