LinuxCommandLibrary
GitHubF-DroidGoogle Play Store

Shell Scripting

Script Basics

A shell script is a text file containing commands that the shell executes in sequence. The first line should be a shebang that tells the system which interpreter to use.
$ #!/bin/bash
copy
$ #!/usr/bin/env bash
copy
Make the script executable and run it.
$ chmod +x script.sh
copy
$ ./script.sh
copy
Use set options at the top of scripts to catch errors early: -e exits on the first error, -u treats unset variables as errors, -o pipefail makes a pipeline fail if any command in it fails.
$ set -euo pipefail
copy

Variables

Variable assignment has no spaces around the = sign. Use double quotes around variable references to prevent word splitting.
ExpressionDescription
VAR=valueAssign a value
VAR=$(command)Capture command output (command substitution)
$VARRead the value
${VAR}Read with explicit boundary
"$VAR"Read safely with quoting
${VAR:-default}Use default if VAR is unset or empty
${VAR:=default}Assign default if VAR is unset or empty
${VAR:+alternate}Use alternate if VAR is set and not empty
${VAR:?error msg}Exit with error if VAR is unset or empty
${#VAR}Length of the value
readonly VARMake variable read-only
export VARMake variable available to child processes

Quoting

Quoting controls what the shell expands. When in doubt, use double quotes.
ExpressionDescription
'text'Single quotes: everything is literal
"text"Double quotes: $variables and $(commands) are expanded
\$HOMEBackslash escapes a single character
Always quote variable references: "$VAR" survives spaces in the value, unquoted $VAR gets split into separate words.

Special Variables

These are set automatically by the shell inside a running script.
VariableDescription
$0Name of the script
$1..$9Positional parameters (arguments)
${10}Positional parameters beyond 9
$#Number of arguments
$@All arguments as separate words
$*All arguments as a single string
$?Exit status of the last command
$$PID of the current shell
$!PID of the last background command
$_Last argument of the previous command
Prefer "$@" when passing arguments on to another command: it preserves each argument as a separate word.

String Operations

Bash provides built-in string manipulation without needing external commands.
ExpressionDescription
${VAR#pattern}Remove shortest match from start
${VAR##pattern}Remove longest match from start
${VAR%pattern}Remove shortest match from end
${VAR%%pattern}Remove longest match from end
${VAR/old/new}Replace first occurrence
${VAR//old/new}Replace all occurrences
${VAR:offset}Substring from offset
${VAR:offset:length}Substring from offset with length
${VAR^}Uppercase first character
${VAR^^}Uppercase all characters
${VAR,}Lowercase first character
${VAR,,}Lowercase all characters
Case conversion (${VAR^^}, ${VAR,,}) requires bash 4 or later.

Conditionals

Use [[ ]] for conditionals in bash scripts. It supports pattern and regex matching and is safer than the older [ ] form. The spaces inside the brackets are required.
$ if [[ -f "file.txt" ]]; then echo "exists"; fi
copy
OperatorDescription
-f fileTrue if file exists and is a regular file
-d fileTrue if file exists and is a directory
-e fileTrue if file exists (any type)
-r fileTrue if file is readable
-w fileTrue if file is writable
-x fileTrue if file is executable
-s fileTrue if file exists and is not empty
-z stringTrue if string is empty
-n stringTrue if string is not empty
==String equality (right side is a glob pattern)
!=String inequality
=~Regex match (inside [[ ]])
-eqNumeric equality
-neNumeric inequality
-ltNumeric less than
-leNumeric less than or equal
-gtNumeric greater than
-geNumeric greater than or equal
$ if [[ "$count" -gt 0 ]]; then echo "positive"; elif [[ "$count" -eq 0 ]]; then echo "zero"; else echo "negative"; fi
copy
$ if [[ "$input" =~ ^[0-9]+$ ]]; then echo "is a number"; fi
copy
The case statement matches a value against glob patterns.
$ case "$1" in start) echo "Starting";; stop) echo "Stopping";; *) echo "Usage: $0 {start|stop}";; esac
copy

Loops

The for loop iterates over a list of items.
$ for file in *.txt; do echo "$file"; done
copy
$ for i in {1..10}; do echo "$i"; done
copy
$ for ((i=0; i<10; i++)); do echo "$i"; done
copy
The while loop runs as long as the condition is true. This is the safe way to read a file line by line.
$ while IFS= read -r line; do echo "$line"; done < file.txt
copy
The until loop runs until the condition becomes true.
$ until [[ -f "ready.flag" ]]; do sleep 1; done
copy
Use break to exit a loop early and continue to skip to the next iteration.

Functions

Functions group reusable commands. Arguments are accessed with $1, $2, etc. inside the function body. Use local to keep variables scoped to the function.
$ greet() { local name="$1"; echo "Hello, $name"; }
copy
$ greet "World"
copy
A function returns its last command's exit status, or use return to set an explicit exit code (0-255). To return strings, use command substitution.
$ get_date() { date +%Y-%m-%d; }
copy
$ today=$(get_date)
copy

Arrays

Bash supports indexed arrays. Declare and manipulate them as follows.
ExpressionDescription
arr=(a b c)Declare an array
arr[0]=valueSet element by index
${arr[0]}Access element by index
"${arr[@]}"All elements, one word each
${#arr[@]}Number of elements
${arr[@]:1:2}Slice: 2 elements starting at index 1
arr+=(d e)Append elements
${!arr[@]}All indices
unset 'arr[1]'Remove an element (indices keep a gap)
Loop over all elements with the array quoted, so elements with spaces stay intact.
$ for item in "${arr[@]}"; do echo "$item"; done
copy
Associative arrays (string keys) require explicit declaration.
$ declare -A map
copy
$ map[key]="value"
copy
$ echo "${map[key]}"
copy

Arithmetic

Use $(( )) for arithmetic expressions and (( )) for arithmetic statements.
ExpressionDescription
$((a + b))Addition
$((a - b))Subtraction
$((a * b))Multiplication
$((a / b))Integer division
$((a % b))Modulo
$((a ** b))Exponentiation
$((a++))Post-increment
$((++a))Pre-increment
((a += 5))Arithmetic assignment
Shell arithmetic is integer only. Pipe to bc for floating point math.
$ echo "scale=2; 10 / 3" | bc
copy

Input and Output

Use read to get input from the user or from a file.
$ read -p "Enter name: " name
copy
$ read -s -p "Password: " pass
copy
$ read -r -a items <<< "a b c"
copy
Use printf for formatted output, it is more portable and predictable than echo.
$ printf "Name: %s, Age: %d\n" "$name" "$age"
copy
A here document passes multi-line text to a command's standard input.
$ cat <<EOF
Hello, $USER
Your home is $HOME
EOF
copy
Use <<'EOF' (quoted) to prevent variable expansion inside the here document.

Exit Codes and Traps

Every command returns an exit code: 0 means success, anything else means failure. The code of the last command is in $?. Use exit to set the script's own exit code.
$ command || exit 1
copy
$ command && echo "succeeded"
copy
Use trap to run cleanup code when the script exits or receives a signal. The EXIT trap runs on any exit, including errors.
$ tmpfile=$(mktemp)
copy
$ trap 'rm -f "$tmpfile"' EXIT
copy
$ trap 'echo "Interrupted"; exit 130' INT TERM
copy

Debugging

Check the syntax without executing the script.
$ bash -n script.sh
copy
Trace every command as it runs, with variables expanded.
$ bash -x script.sh
copy
Turn tracing on and off inside a script.
$ set -x
copy
$ set +x
copy
Lint scripts for common mistakes and pitfalls.
$ shellcheck script.sh
copy
Copied to clipboard
Kai