Shell Scripting
7 pages · ~5 min✓ Reviewed
Builds on Text Processing. Next up: Processes and Signals.
Shebang and Variables
1. Shebang and Running
- Shebang is line 1 only; the kernel reads it on exec
#!/usr/bin/env bashpicks bash from PATH#!/bin/bashis a fixed path
| Run | Shebang | Runs in | Vars persist |
|---|---|---|---|
chmod +x s.sh && ./s.sh | used | child | no |
bash s.sh | ignored | child | no |
source s.sh | ignored | current shell | yes |
2. Variables
bash
name=world # no spaces around = greet=${name:-hi} # fallback if unset or empty now=$(date +%F) # command substitution n=$((n+1)) # arithmetic readonly MAX=3 export MODE=dev
readonlyblocks reassignment;exportshows it to child processes- Untyped strings; global unless
localin a function
3. Interview Point
- Why env bash? It finds bash on PATH, so it works where bash is not in /bin
- ./s.sh vs source?
./runs a child shell;sourceruns in the current one - Why does
x = 5fail? The spaces makexa command with two arguments
Quoting and Expansion Rules
1. Quotes and Splitting
- Unquoted
$varis word-split and glob-expanded f='my file.txt';rm $fgets two args- Always write "$var"
"..."keeps$var,$(cmd);\escapes only$backtick"\'...'is fully literal
| Form | Prints |
|---|---|
echo "$HOME" | the path |
echo '$HOME' | $HOME |
"a\nb" | stays literal |
2. Forwarding Arguments
| Form | Expands to |
|---|---|
"$@" | each arg as a separate word: forward with this |
"$*" | all args joined into one string |
3. Tests
[[ ]]: left side needs no quotes;==is pattern match[ ]: unquoted empty var gives a syntax error
4. Interview Point
rm $dir/*with a space or empty$dir? Splits into extra args; empty becomesrm /*- Single vs double quotes? Single is literal; double expands
$and$(cmd) "$@"vs"$*"? Separate words vs one string
Remember
Quote every $var; forward arguments with "$@".
Conditionals and Loops
1. Tests Run Commands
if cmd; then ...; elif ...; else ...; fitests exit status- 0 = true, nonzero = false
[ ]and[[ ]]are just commands returning a status
[ ] | [[ ]] | |
|---|---|---|
| Origin | POSIX | bash-native |
| Quoting | needed | no splitting |
| And / or | -a -o | && || |
| Regex | no | =~ |
2. Common Tests
-ffile,-ddir,-eexists-zempty string,-nnon-empty-eq -lt -gtintegers;==strings
3. Loops
bash
for f in *.log; do echo "$f"; done for ((i=0;i<5;i++)); do echo "$i"; done while read -r line; do echo "$line"; done < file until cmd; do sleep 1; done
break leaves the loop, continue skips to the next pass
4. Interview Point
[ ]vs[[ ]]? POSIX command vs bash keyword: no splitting,&&,=~- Why
while read -r?forover$(cat f)splits on spaces;-rkeeps backslashes if grep -q x ftests? grep's exit status: 0 when a match is found
Functions, Arguments and Exit Status
1. Functions and Arguments
- Define
name() { ...; }, call it like a command - Inside:
$1 $2 ...and$# local x=1keeps x out of the global scope
| Item | Means |
|---|---|
$0 | script name |
$1..$9 | positional |
${10} | needs braces |
$# / $@ | count / all args |
shift | drops $1 |
${1:-fallback} | default if unset |
2. Exit Status
return nandexit nset a status 0-255returnleaves the function;exitends the script$?= last status, overwritten by the next commandexit 0= success, anything else = failure- Functions return a status; data goes out via stdout:
result=$(fn) - Test success with
if fn; then
bash
greet() { echo "hi $1"; }
msg=$(greet bob)
check() { [[ -f "$1" ]]; }
if check /etc/passwd; then echo ok; fi
echo "$msg"output
ok hi bob
Strict Mode and Cron
1. Strict mode
| Flag | Effect |
|---|---|
set -e | exit on first failing command |
set -u | unset variable is an error |
set -o pipefail | pipeline fails if any stage fails |
- Put set -euo pipefail at the top of the script
set -eskipsif/whileconditions- Also skips any
&&/||command except the last:false && xlives,true && falseexits - Without pipefail,
false | truereturns 0 and hides the failure
bash
false | true; echo $? set -o pipefail false | true; echo $?
output
0 1
2. Cron
- Fields:
min hour dom mon dow command crontab -eedits,crontab -llists
| Schedule | Runs |
|---|---|
*/5 * * * * | every 5 minutes |
0 2 * * 1 | 02:00 on Mondays |
Works in terminal, fails in cron
Cron has a minimal PATH and no login environment. Use absolute paths, set PATH= at the top, and add >> /var/log/job.log 2>&1 or output mail is lost.
Bash Scripting Cheat Sheet
1. Header and Specials
bash
#!/usr/bin/env bash set -euo pipefail v="${1:-x}" now=$(date +%F) n=$(( $# + 1 ))
default, substitution, math
- Run:
chmod +x s.sh, then./s.sh - Always quote:
"$var"
| Var | Meaning |
|---|---|
$? | last status |
$# | arg count |
$@ | all args |
$0 | script name |
$$ | PID |
2. Tests, Loops, Functions
| Syntax | Meaning |
|---|---|
[[ -f f ]] | file exists |
[[ -z "$v" ]] | empty string |
[[ $a -lt $b ]] | int less than |
for x in a b; do | loop a list |
while read -r l; do | loop lines |
f() { local x=$1; } | function, local var |
3. Cron
- mmin
- hhour
- domday
- monmonth
- dowweekday
- cmdcommand
*/5 * * * *every 5 minutes0 2 * * 102:00 Mondayscrontab -eedit,-llist
Cron
Absolute paths; add >> log 2>&1
Check yourself
f='my file.txt'; rm $f — what does rm receive, and what is the fix?
- Two arguments:
myandfile.txt - Unquoted expansion is word-split
- Fix:
rm "$f"
What does false | true; echo $? print without pipefail, and with it?
0without: the pipeline takes the last stage's status1withset -o pipefail: any failing stage fails the pipe
Under set -e, [ -f nope ] && echo yes is followed by echo done. Does the script stop?
- No, it prints
done - A failing non-last command in an
&&list does not triggerset -e