All articles
4 min read

Comprehensive Bash Programming Guide

A practical Bash scripting reference covering variables, conditionals, loops, functions, arrays, string operations, error handling, and script structure with working examples.

Bash scripts automate repetitive tasks, glue tools together, and form the backbone of Linux system administration. This guide covers the core language features you need to write useful, reliable scripts.

Script Structure

Every Bash script starts with a shebang line and should be made executable:

#!/usr/bin/env bash
set -euo pipefail   # exit on error, unset variable, pipe failure

# Your script here
echo "Hello, World!"

set -euo pipefail is considered best practice — it makes scripts fail loudly instead of silently continuing after errors.

Make executable and run:

chmod +x myscript.sh
./myscript.sh

Variables

NAME="Alice"
echo "Hello, $NAME"
echo "Hello, ${NAME}!"   # braces required before other characters

readonly MAX=100         # constant
unset NAME               # delete a variable

Positional parameters (script arguments):

echo "Script: $0"
echo "First arg: $1"
echo "All args: $@"
echo "Arg count: $#"

Special variables:

$?   # exit code of last command (0 = success)
$$   # current process ID
$!   # PID of last background process

User Input

read -p "Enter your name: " NAME
echo "Hello, $NAME"

read -s -p "Password: " PASS   # -s hides input
echo ""

Conditional Statements

if [ "$NAME" == "Alice" ]; then
    echo "Hello, Alice"
elif [ "$NAME" == "Bob" ]; then
    echo "Hello, Bob"
else
    echo "Hello, stranger"
fi

Use [[ ]] for more powerful conditions (supports regex, no word splitting):

if [[ "$NAME" =~ ^A ]]; then
    echo "Name starts with A"
fi

Comparisons

Numeric:

if [ "$A" -eq "$B" ]; then ...    # equal
if [ "$A" -ne "$B" ]; then ...    # not equal
if [ "$A" -gt "$B" ]; then ...    # greater than
if [ "$A" -lt "$B" ]; then ...    # less than
if [ "$A" -ge "$B" ]; then ...    # greater or equal
if [ "$A" -le "$B" ]; then ...    # less or equal

String:

if [ "$A" == "$B" ]; then ...     # equal
if [ "$A" != "$B" ]; then ...     # not equal
if [ -z "$A" ]; then ...          # empty string
if [ -n "$A" ]; then ...          # non-empty string

File conditions:

if [ -e "$FILE" ]; then ...       # exists
if [ -f "$FILE" ]; then ...       # is a regular file
if [ -d "$DIR" ]; then ...        # is a directory
if [ -r "$FILE" ]; then ...       # readable
if [ -w "$FILE" ]; then ...       # writable
if [ -x "$FILE" ]; then ...       # executable
if [ -s "$FILE" ]; then ...       # non-zero size

Case Statement

read -p "Enter y/n: " ANSWER
case "$ANSWER" in
    [yY] | [yY][eE][sS])
        echo "Yes";;
    [nN] | [nN][oO])
        echo "No";;
    *)
        echo "Invalid input";;
esac

Loops

For loop over a list:

for NAME in Alice Bob Carol; do
    echo "Hello, $NAME"
done

For loop over files:

for FILE in *.txt; do
    echo "Processing $FILE"
done

C-style for loop:

for ((i = 1; i <= 5; i++)); do
    echo "Iteration $i"
done

While loop:

COUNT=0
while [ $COUNT -lt 5 ]; do
    echo "Count: $COUNT"
    ((COUNT++))
done

Read a file line by line:

while IFS= read -r LINE; do
    echo "$LINE"
done < "input.txt"

Arrays

FRUITS=("apple" "banana" "cherry")

echo "${FRUITS[0]}"          # first element
echo "${FRUITS[@]}"          # all elements
echo "${#FRUITS[@]}"         # length

FRUITS+=("date")             # append

for FRUIT in "${FRUITS[@]}"; do
    echo "$FRUIT"
done

Associative arrays (Bash 4+):

declare -A COLORS
COLORS["apple"]="red"
COLORS["banana"]="yellow"
echo "${COLORS["apple"]}"

String Operations

STR="Hello, World!"

echo "${#STR}"            # length: 13
echo "${STR:7:5}"         # substring: World
echo "${STR,,}"           # lowercase: hello, world!
echo "${STR^^}"           # uppercase: HELLO, WORLD!
echo "${STR/World/Bash}"  # replace first: Hello, Bash!
echo "${STR//l/L}"        # replace all: HeLLo, WorLd!
echo "${STR#Hello, }"     # strip prefix: World!
echo "${STR%!}"           # strip suffix: Hello, World

Arithmetic

A=10
B=3

echo $((A + B))    # 13
echo $((A - B))    # 7
echo $((A * B))    # 30
echo $((A / B))    # 3 (integer division)
echo $((A % B))    # 1 (modulo)
echo $((A ** B))   # 1000 (exponent)

((A++))            # increment in place
((A += 5))

Functions

greet() {
    local NAME="$1"        # local variable — doesn't leak outside function
    local GREETING="${2:-Hello}"   # default value if $2 is empty
    echo "$GREETING, $NAME!"
}

greet "Alice"          # Hello, Alice!
greet "Bob" "Hi"       # Hi, Bob!

Functions return exit codes (0–255), not values. To return a string, use echo and command substitution:

get_date() {
    echo "$(date +%Y-%m-%d)"
}
TODAY=$(get_date)
echo "Today is $TODAY"

Error Handling

# Check exit code explicitly
cp source.txt dest.txt
if [ $? -ne 0 ]; then
    echo "Copy failed" >&2
    exit 1
fi

# Or use the || shorthand
cp source.txt dest.txt || { echo "Copy failed" >&2; exit 1; }

# Trap to clean up on exit
TMP=$(mktemp)
trap "rm -f $TMP" EXIT    # runs cleanup when script exits for any reason

Heredoc

Write multi-line strings without quoting issues:

cat << EOF > config.txt
server = localhost
port = 8080
debug = true
EOF

Conclusion

Bash scripting rewards practice — start small with automation scripts for tasks you do manually, add set -euo pipefail to every script, and use shellcheck (a static analysis tool) to catch common mistakes before they cause problems in production. The patterns here cover the vast majority of real-world scripting needs.