CHEAT SHEET
Bash cheat sheet
A Bash scripting quick reference, written for study. Copy any block with one click. Syntax is for Bash 4 and newer; current RHEL ships Bash 5.
Getting started
#!/usr/bin/env bash# hello.shecho "Hello, $USER"
chmod +x hello.sh # make it executable./hello.sh # run itbash hello.sh # run it without chmod
The shebang goes on the very first line. #!/bin/bash works too on RHEL.
set -euo pipefail # stop on errors, unset variables and failed pipesVariables
name="Abdallah" # no spaces around =echo "$name" # always quote expansionsecho "${name}_file" # braces separate the namereadonly PI=3.14 # constantunset name # remove a variableexport PATH="$PATH:$HOME/bin" # visible to child processestoday=$(date +%F) # command substitutionn=$((3 + 4)) # arithmetic
Special variables
| Variable | Meaning |
|---|---|
| $0 | Name of the script |
| $1 ... $9 | Positional arguments |
| $# | Number of arguments |
| "$@" | All arguments, each one kept as a separate word |
| "$*" | All arguments joined into one single word |
| $? | Exit status of the last command |
| $$ | PID of the current shell |
| $! | PID of the last background job |
| $_ | Last argument of the previous command |
| $LINENO | Current line number in the script |
| $RANDOM | A random number from 0 to 32767 |
| $SECONDS | Seconds since the shell started |
| $EUID | Effective user ID (0 means root) |
Parameter expansion
${var:-default} # use default if var is unset or empty${var:=default} # assign default if var is unset or empty${var:?message} # print message and exit if unset or empty${var:+alt} # alt if var is set and not empty${#var} # length of the value${var:2:3} # substring: offset 2, length 3${var#prefix} # remove the shortest matching prefix${var##*/} # remove the longest prefix (like basename)${var%suffix} # remove the shortest matching suffix${var%%.*} # remove the longest suffix${var/old/new} # replace the first match${var//old/new} # replace every match${var^^} # UPPERCASE${var,,} # lowercase
Strings and quoting
| Syntax | Meaning |
|---|---|
| 'text' | Literal text, nothing is expanded |
| "text" | Expands $variables, $(commands) and backslash escapes |
| $'a\tb' | ANSI-C quoting: \t, \n and similar escapes work |
| \$ | Escape a single character |
cat <<EOFHello $USEREOFcat <<'EOF' # quoted marker: no expansion inside$HOME stays literalEOFgrep foo <<< "$text" # here-string: feed a variable to stdin
Conditionals
if [[ -f "$file" ]]; then echo "file exists"elif [[ -d "$file" ]]; then echo "directory"else echo "missing"fi[[ -z "$var" ]] && echo "empty" # && runs when the test is true[[ -n "$var" ]] || echo "unset" # || runs when the test is false
case "$1" in start) echo "starting" ;; stop) echo "stopping" ;; *) echo "usage: $0 {start|stop}" ;;esac
Test operators
| Test | True when |
|---|---|
| -e file | Exists |
| -f file | Is a regular file |
| -d dir | Is a directory |
| -L file | Is a symbolic link |
| -r -w -x file | Readable, writable, executable |
| -s file | Exists and is not empty |
| -z str | String is empty |
| -n str | String is not empty |
| str == pattern | Equal (a glob pattern inside [[ ]]) |
| str != pattern | Not equal |
| str =~ regex | Matches an extended regular expression |
| a -eq b | Integers: equal |
| -ne -lt -le -gt -ge | Integers: not equal, less, less or equal, greater, greater or equal |
| (( a < b )) | Arithmetic comparison |
| ! && || | Not, and, or |
Loops
for i in 1 2 3; do echo "$i"; donefor i in {1..5}; do echo "$i"; done # brace rangefor ((i = 0; i < 5; i++)); do echo "$i"; done # C-stylefor f in *.log; do echo "$f"; done # glob (quote "$f" when you use it)while read -r line; do echo "$line"; done < file.txt # file, line by linewhile true; do sleep 1; done # foreveruntil ping -c1 host &>/dev/null; do sleep 1; done # repeat until it succeedsbreak # leave the loopcontinue # skip to the next round
Functions
greet() { local name="${1:-world}" # local keeps the variable inside the function echo "Hello, $name"}greet "Abdallah" # call itresult=$(greet "Abdallah") # capture its output
is_root() { [[ $EUID -eq 0 ]] # the last command's status is the return value}if is_root; then echo "root"; fi
Arrays
arr=(one two "three four")echo "${arr[0]}" # first elementecho "${arr[@]}" # all elementsecho "${#arr[@]}" # number of elementsecho "${!arr[@]}" # indexesecho "${arr[@]:1:2}" # slice: from index 1, two elementsarr+=(five) # appendunset 'arr[1]' # delete one elementfor x in "${arr[@]}"; do echo "$x"; done # loop safely
Associative arrays
Needs Bash 4 or newer.
declare -A portsports[http]=80ports[https]=443echo "${ports[http]}" # one valueecho "${!ports[@]}" # all keysecho "${ports[@]}" # all valuesfor k in "${!ports[@]}"; do echo "$k=${ports[$k]}"; done
Redirection and pipes
cmd > file # stdout to a file (overwrite)cmd >> file # appendcmd 2> err.log # stderr to a filecmd &> all.log # stdout and stderr to a filecmd > out 2>&1 # same thing, portable formcmd < file # a file as stdincmd1 | cmd2 # stdout of cmd1 into cmd2cmd |& tee run.log # stdout and stderr, shown and savedcmd > /dev/null 2>&1 # throw all output awayecho "error" >&2 # write to stderr
Chaining and jobs
cmd1 && cmd2 # cmd2 only if cmd1 succeedscmd1 || cmd2 # cmd2 only if cmd1 failscmd1 ; cmd2 # run both, always(cd /tmp && ls) # subshell: the cd does not leak out{ echo a; echo b; } > out # group commands (note the spaces and ;)cmd & # run in the backgroundjobs # list background jobsfg %1 # bring job 1 to the foregroundwait # wait for all background jobsnohup cmd & # keep running after you log out
Expansions
echo $(date +%F) # command substitutiondiff <(sort a) <(sort b) # process substitutionecho {a,b,c}.txt # a.txt b.txt c.txtecho file{1..3} # file1 file2 file3echo ~ # your home directoryecho *.txt # globecho "$((2 ** 8))" # 256
Input and options
read -r -p "Name: " name # prompt and read a lineread -r -s -p "Password: " pw # silent inputwhile getopts "ab:" opt; do # options: -a and -b value case $opt in a) echo "flag a" ;; b) echo "b=$OPTARG" ;; *) exit 1 ;; esacdoneshift $((OPTIND - 1)) # drop the parsed options[[ $# -ge 1 ]] || { echo "usage: $0 file" >&2; exit 1; }
Errors and traps
set -euo pipefail # strict modetmp=$(mktemp) # temporary filetrap 'rm -f "$tmp"' EXIT # always clean uptrap 'echo "failed on line $LINENO" >&2' ERRcommand || true # allow this one to failif ! command; then echo "failed"; fi # handle the failure yourselfexit 0 # leave with a status
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Misuse of a shell builtin or wrong usage |
| 126 | Found, but not executable |
| 127 | Command not found |
| 130 | Stopped with Ctrl+C |
| 128 + n | Killed by signal n |
Debugging
bash -n script.sh # check the syntax onlybash -x script.sh # trace every command as it runsset -x # turn tracing on inside a scriptset +x # turn it off againshellcheck script.sh # lint it (ShellCheck is a separate tool)
Text one-liners
grep -rn "pattern" . # search recursively, with line numberssed -i 's/old/new/g' file # replace in placeawk '{print $1}' file # first columncut -d: -f1 /etc/passwd # first field, ":" delimitedsort file | uniq -c | sort -nr # count duplicates, most common firsttr 'a-z' 'A-Z' < file # uppercasewc -l file # count linestail -f /var/log/messages # follow a log
Handy idioms
dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" # folder of this script[[ $EUID -eq 0 ]] || { echo "run as root" >&2; exit 1; } # require rootcommand -v git >/dev/null || { echo "git missing" >&2; exit 1; } # require a commandmkdir -p "$dir/out" # create it if missingwhile IFS=, read -r a b c; do echo "$a $b"; done < data.csv # split CSV fieldsfind . -name "*.log" -print0 | xargs -0 rm # safe with spaces in names
More cheat sheets
Questions
How do I start a Bash script?
Put #!/usr/bin/env bash (or #!/bin/bash) on the first line, make the file executable with chmod +x script.sh, and run it with ./script.sh.
What does set -euo pipefail do?
-e stops the script when a command fails, -u treats unset variables as errors, and -o pipefail makes a pipeline fail if any command in it fails.
What is the difference between $@ and $*?
Quoted, "$@" expands to each argument as its own word, while "$*" joins all arguments into a single word. Use "$@" in almost every case.
How do I check if a file exists in Bash?
Use [[ -f "$file" ]] for a regular file, [[ -d "$dir" ]] for a directory, or [[ -e "$path" ]] for either one.
How do I run a command only if the previous one succeeded?
Use &&, for example mkdir build && cd build. Use || to run the second command only when the first one fails.