Advanced Bash Scripting: Exit Codes, String Handling, Logic, and Control Flow

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.

Tags: bash shell-scripting exit-status string-manipulation control-flow

Posted on Mon, 05 Oct 2026 16:49:52 +0000 by Garth Farley