Chapter 10: Linux Shell Scripting

22 min read ▅▅ Intermediate Updated July 2026

Learning Objectives

  • Write and run Bash scripts with a correct shebang and permissions.
  • Use variables, environment variables, user input, and command-line arguments.
  • Interpret exit status correctly (including command-specific non-zero codes).
  • Use conditionals, file tests, and case for decision making.
  • Write for, while, and until loops, plus functions.
  • Debug with bash -x / bash -n and understand set -e, set -u, and pipefail.
  • Schedule scripts with cron and build a practical disk-usage monitor.

What is Shell Scripting?

A shell script is a text file of commands run in sequence by a shell interpreter. Admins use scripts for repetitive work such as backups, monitoring, patching, health checks, log cleanup, and user management.

Shell Architecture

User ──► Shell (bash) ──► Linux Kernel ──► Hardware
              

Common Linux Shells

ShellDescription
shBourne Shell
bashBourne Again Shell (Default)
kshKorn Shell (Common on AIX)
zshAdvanced interactive shell
fishFriendly Interactive Shell

Your First Script

touch hello.sh
vi hello.sh
#!/bin/bash
echo "Welcome to Linux Shell Scripting"

Run a Script

chmod +x hello.sh
./hello.sh
bash hello.sh

Prefer ./hello.sh (uses the shebang) or bash hello.sh. Avoid sh hello.sh on many Linux systems: sh is often dash, which does not support Bash-only features (arrays, [[ ]], bashisms in general) and can fail silently or with cryptic errors.

Understanding the Shebang

#!/bin/bash

The shebang tells Linux which interpreter should execute the script.

Comments

# This is a comment

Comments improve readability and maintenance.

Variables

NAME="Ashutosh"
echo "$NAME"

Best Practice

Do not place spaces around the '=' operator while assigning variables. Quote expansions ("$NAME") so spaces and glob characters stay safe.

Environment Variables

echo "$HOME"
echo "$USER"
echo "$PATH"
echo "$SHELL"
echo "$0"
ps -p $$ -o comm=
env
VariableDescription
HOMEUser home directory
USERCurrent user
PATHExecutable search path
SHELLLogin shell (from the account database), not necessarily the shell running right now

$SHELL is your configured login shell (for example /bin/bash). The interpreter currently running the script or interactive session is separate: check echo "$0", or ps -p $$ -o comm=, for the process that is actually executing.

User Input

read -r NAME
echo "Welcome $NAME"

Command-Line Arguments

#!/bin/bash
# save as script.sh, then: ./script.sh John Delhi
echo "Script: $0"
echo "Name: $1"
echo "City: $2"
echo "Argument count: $#"
echo "All args as one string: $*"
printf 'Arg: %s\n' "$@"
ParameterMeaning
$0Script name
$1First argument
$2Second argument
$#Argument count
"$*"All arguments as one string (IFS-joined)
"$@"All arguments as separate words (preserve when looping or passing on)

With ./script.sh John Delhi, $1 is John and $2 is Delhi. Prefer "$@" when forwarding arguments; use "$*" when you intentionally want one string. Avoid echo "All args: $@" — inside a larger double-quoted string, $@ does not safely expand as separate arguments.

Exit Status

ls /tmp
echo $?

grep "nomatch" /etc/passwd
echo $?   # often 1 — pattern not found, not a shell crash

Exit code 0 means success. A non-zero value is defined by the command: it often signals failure, but not always an error in the everyday sense. For example, grep returns 1 when no lines match, and 2 for real errors (bad options, missing files).

Command Substitution

TODAY=$(date)
HOST=$(hostname)
echo "$TODAY"
echo "$HOST"

Arithmetic

A=10
B=20
echo $((A+B))
echo $((A-B))
echo $((A*B))
echo $((A/B))

$((A/B)) uses integer division: $((10/20)) is 0, not 0.5. For floating-point math, use tools such as bc or awk.

Useful Commands

echo
printf
pwd
hostname
whoami
date
clear
exit

Quick System Info Script

#!/bin/bash
echo "Hostname: $(hostname)"
echo "User: $(whoami)"
echo "Date: $(date)"
echo "Uptime: $(uptime -p)"

Decision Making

Conditionals let a script take different actions based on tests. Always quote variables in [ ] tests so empty or spaced values do not break the test.

if Statement

#!/bin/bash
AGE=20
if [ "$AGE" -ge 18 ]
then
  echo "Eligible"
fi

if...else

MARKS=45
if [ "$MARKS" -ge 50 ]
then
  echo "Pass"
else
  echo "Fail"
fi

if...elif...else

SCORE=82
if [ "$SCORE" -ge 90 ]; then
  echo "Grade A"
elif [ "$SCORE" -ge 75 ]; then
  echo "Grade B"
else
  echo "Grade C"
fi

Comparison Operators

OperatorMeaning
-eqEqual
-neNot Equal
-gtGreater Than
-geGreater Than or Equal
-ltLess Than
-leLess Than or Equal

String Comparisons

NAME="linux"
if [ "$NAME" = "linux" ]
then
  echo "Matched"
fi
OperatorDescription
=Equal
!=Not Equal
-zEmpty String
-nNot Empty

case Statement

read -r OPTION
case "$OPTION" in
1) echo "Start";;
2) echo "Stop";;
3) echo "Restart";;
*) echo "Invalid";;
esac

Enterprise Insight

case is a common pattern in service wrappers and simple menu-driven admin scripts.

Looping Constructs (for, while, until)

for Loop

for USER in user1 user2 user3
do
  echo "$USER"
done

while Loop

COUNT=1
while [ "$COUNT" -le 5 ]
do
  echo "$COUNT"
  COUNT=$((COUNT+1))
done

until Loop

COUNT=1
until [ "$COUNT" -gt 5 ]
do
  echo "$COUNT"
  COUNT=$((COUNT+1))
done

until runs while the condition is false — the opposite of while.

Loop Controls (break, continue)

# break example
for i in {1..10}; do
  [ "$i" -eq 5 ] && break
  echo "$i"
done

# continue example
for i in {1..5}; do
  [ "$i" -eq 3 ] && continue
  echo "$i"
done

Functions & Reusable Blocks

greet() {
  echo "Welcome $1"
}
greet "Linux"

Hands-on Practice

  1. Create your first Bash script.
  2. Display today's date and print hostname.
  3. Accept user input and use command-line arguments.
  4. Display HOME and PATH variables.
  5. Perform arithmetic calculations.

Common Mistakes

  • Forgetting the shebang.
  • Not making the script executable.
  • Using spaces around '='.
  • Missing quotes around values with spaces.
  • Using the wrong interpreter.

File Tests

TestDescription
-fRegular file
-dDirectory
-rReadable
-wWritable
-xExecutable
-eExists
f="/etc/passwd"
if [ -f "$f" ]; then
  echo "$f is a regular file"
fi

Text Processing inside Scripts (grep, awk, sed, cut)

grep root /etc/passwd # Search for a pattern
cut -d: -f1 /etc/passwd # Extract fields from delimited text
awk -F: '{print $1,$3}' /etc/passwd # Print selected fields
sed 's/error/ERROR/g' app.log # Replace text in a stream

Debugging Shell Scripts

bash -x script.sh   # Trace each command as it runs
bash -n script.sh   # Syntax check only (no execution)
set -e              # Exit when a command fails (see caveats below)
set -u              # Error on unset variables
set -o pipefail     # Pipeline fails if any stage fails

set -e exits when a simple command fails, but failures inside if/while/until tests, or on the left of &&/||, are usually exempt. In a pipeline without pipefail, only the last command’s status matters. set -u catches typos in variable names early, but breaks intentional optional parameters unless you guard them. pipefail is stricter and safer for pipelines, yet can surprise scripts that ignored mid-pipe failures before.

Scheduling Tasks with Cron

crontab -e
# Use absolute paths; cron has a minimal PATH. Redirect output for logging.
0 2 * * * /opt/scripts/backup.sh >>/var/log/backup.log 2>&1

Practical Script Example — Disk Usage Monitor

Prefer machine-readable columns when available. On GNU coreutils, df --output=pcent,target avoids parsing human-oriented fields. The portable fallback below uses df -P and joins mount fields from column 6 to the end of the line so paths with spaces still work.

#!/bin/bash
set -euo pipefail
THRESHOLD=80

# Preferred on GNU df:
# df --output=pcent,target | …

if ! DF_OUT=$(df -P 2>/dev/null); then
  echo "Error: df failed" >&2
  exit 1
fi

# Process substitution keeps the loop in this shell; || true ignores EOF from read
while read -r USE MOUNT; do
  PCT="${USE%\%}"

  case "$PCT" in
    ''|*[!0-9]*) continue ;;
  esac

  case "$MOUNT" in
    /proc|/sys|/dev|/run|/proc/*|/sys/*|/dev/*|/run/*) continue ;;
  esac

  if [ "$PCT" -ge "$THRESHOLD" ]; then
    echo "Warning: $MOUNT is ${PCT}% full"
  fi
done < <(echo "$DF_OUT" | awk 'NR>1 {
  use=$5
  mount=""
  for (i=6; i<=NF; i++) mount = (mount ? mount " " : "") $i
  print use, mount
}') || true

Commands Covered in This Chapter

  • bash -n / bash -x — syntax check and execution trace
  • set -e / set -u / set -o pipefail — stricter error handling (with trade-offs)
  • crontab — schedule recurring jobs
  • grep / awk / sed / cut — search and process text
  • chmod — make a script executable
  • df -P — portable filesystem usage for monitoring scripts