- What is a Shell Script?
- Your First Script
- Variables
- User Input
- Conditionals (if/else)
- Comparison Operators
- Case Statements
- Loops
- Functions
- Exit Codes & Error Handling
- Arrays
- String Manipulation
- Arithmetic
- Debugging Scripts
- Practice Exercises
A shell script is a text file containing a sequence of commands that the shell executes automatically.
๐ Analogy: A recipe is a script for cooking โ it lists ingredients (variables) and step-by-step instructions (commands) to produce a result.
- ๐ Automate repetitive tasks
- ๐ ๏ธ System administration โ backups, monitoring, deployments
- โก DevOps โ CI/CD pipelines, infrastructure automation
- ๐ Glue code โ connect different tools and programs
#!/bin/bash
# My first shell script
# Filename: hello.sh
echo "Hello, World!"
echo "Today is: $(date)"
echo "You are: $(whoami)"
echo "Current directory: $(pwd)"chmod +x hello.sh./hello.sh # Run from current directory
bash hello.sh # Run with bash explicitly#!/bin/bash # Use bash
#!/bin/sh # Use POSIX shell (more portable)
#!/usr/bin/env bash # Find bash in PATH (most portable)
#!/usr/bin/env python3 # Works for other languages too๐ก The shebang (
#!) tells the system which interpreter to use. Always include it as the first line.
# No spaces around the = sign!
name="Sovon" # String
age=25 # Number (stored as string in bash)
today=$(date) # Command output
path=$HOME/Documents # Using another variable
readonly PI=3.14159 # Constant (cannot be changed)๐จ Common Mistake:
name = "Sovon"(with spaces) will NOT work. No spaces around=.
echo "Hello, $name" # Hello, Sovon
echo "Hello, ${name}!" # Hello, Sovon! (use braces for clarity)
echo "I am $age years old"
echo "Today: $today"# By default, variables are local to the script
my_var="local"
# Export to make available to child processes
export MY_VAR="global"
# Or inline for a single command
MY_VAR="temp" ./myscript.sh
# Unset a variable
unset my_var| Variable | Description |
|---|---|
$0 |
Script name |
$1 to $9 |
Positional arguments |
$# |
Number of arguments |
$@ |
All arguments (as separate words) |
$* |
All arguments (as single string) |
$? |
Exit code of last command |
$$ |
PID of current script |
$! |
PID of last background process |
$_ |
Last argument of previous command |
#!/bin/bash
echo "Script: $0"
echo "First arg: $1"
echo "All args: $@"
echo "Number of args: $#"name="World"
echo "Hello, $name" # Double quotes: variables ARE expanded
# Output: Hello, World
echo 'Hello, $name' # Single quotes: variables NOT expanded
# Output: Hello, $name
echo "Today is $(date)" # Command substitution works in double quotes
echo "The file has $(wc -l < file.txt) lines"#!/bin/bash
# Simple input
read -p "Enter your name: " username
echo "Hello, $username!"
# Silent input (for passwords)
read -sp "Enter password: " password
echo # New line after hidden input
echo "Password received."
# Input with default value
read -p "Enter color [blue]: " color
color=${color:-blue} # Default to "blue" if empty
echo "Color: $color"
# Read multiple values
read -p "Enter first and last name: " first last
echo "First: $first, Last: $last"
# Read with timeout
read -t 5 -p "Quick! Enter something (5s): " answer#!/bin/bash
# Basic if
if [ "$1" = "hello" ]; then
echo "Hi there!"
fi
# if-else
age=$1
if [ "$age" -ge 18 ]; then
echo "You are an adult."
else
echo "You are a minor."
fi
# if-elif-else
if [ "$age" -lt 13 ]; then
echo "Child"
elif [ "$age" -lt 18 ]; then
echo "Teenager"
elif [ "$age" -lt 65 ]; then
echo "Adult"
else
echo "Senior"
fi# [[ ]] is bash-specific but more powerful than [ ]
if [[ "$name" == "Sovon" ]]; then
echo "Hello, Sovon!"
fi
# Supports regex matching
if [[ "$email" =~ ^[a-zA-Z]+@[a-zA-Z]+\.[a-zA-Z]+$ ]]; then
echo "Valid email format"
fi
# Supports pattern matching
if [[ "$file" == *.txt ]]; then
echo "It's a text file"
fi| Operator | Meaning | Example |
|---|---|---|
-eq |
Equal | [ "$a" -eq "$b" ] |
-ne |
Not equal | [ "$a" -ne "$b" ] |
-gt |
Greater than | [ "$a" -gt "$b" ] |
-ge |
Greater or equal | [ "$a" -ge "$b" ] |
-lt |
Less than | [ "$a" -lt "$b" ] |
-le |
Less or equal | [ "$a" -le "$b" ] |
| Operator | Meaning | Example |
|---|---|---|
= or == |
Equal | [ "$a" = "$b" ] |
!= |
Not equal | [ "$a" != "$b" ] |
-z |
Empty string | [ -z "$a" ] |
-n |
Not empty | [ -n "$a" ] |
< |
Less than (alpha) | [[ "$a" < "$b" ]] |
> |
Greater than (alpha) | [[ "$a" > "$b" ]] |
| Operator | True If... |
|---|---|
-e file |
File exists |
-f file |
Is a regular file |
-d file |
Is a directory |
-r file |
Is readable |
-w file |
Is writable |
-x file |
Is executable |
-s file |
File size > 0 |
-L file |
Is a symbolic link |
f1 -nt f2 |
f1 is newer than f2 |
f1 -ot f2 |
f1 is older than f2 |
#!/bin/bash
file=$1
if [ -e "$file" ]; then
echo "$file exists"
if [ -f "$file" ]; then echo " It's a regular file"; fi
if [ -d "$file" ]; then echo " It's a directory"; fi
if [ -r "$file" ]; then echo " It's readable"; fi
if [ -w "$file" ]; then echo " It's writable"; fi
if [ -x "$file" ]; then echo " It's executable"; fi
else
echo "$file does not exist"
fi# AND
if [ "$a" -gt 0 ] && [ "$a" -lt 100 ]; then echo "Between 1-99"; fi
if [[ "$a" -gt 0 && "$a" -lt 100 ]]; then echo "Between 1-99"; fi
# OR
if [ "$a" = "yes" ] || [ "$a" = "y" ]; then echo "Confirmed"; fi
if [[ "$a" = "yes" || "$a" = "y" ]]; then echo "Confirmed"; fi
# NOT
if [ ! -f "$file" ]; then echo "File does not exist"; fi
if ! grep -q "error" log.txt; then echo "No errors found"; fi#!/bin/bash
read -p "Enter a fruit: " fruit
case $fruit in
apple|Apple)
echo "๐ Apples are red!"
;;
banana|Banana)
echo "๐ Bananas are yellow!"
;;
orange|Orange)
echo "๐ Oranges are orange!"
;;
*)
echo "๐คท Unknown fruit: $fruit"
;;
esac#!/bin/bash
case "$1" in
start)
echo "Starting service..."
;;
stop)
echo "Stopping service..."
;;
restart)
echo "Restarting service..."
;;
status)
echo "Checking status..."
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac# Basic for loop
for i in 1 2 3 4 5; do
echo "Number: $i"
done
# C-style for loop
for ((i=0; i<10; i++)); do
echo "Count: $i"
done
# Range (brace expansion)
for i in {1..10}; do echo "$i"; done
for i in {0..100..5}; do echo "$i"; done # Step by 5
# Loop over files
for file in *.txt; do
echo "Processing: $file"
done
# Loop over command output
for user in $(cut -d: -f1 /etc/passwd); do
echo "User: $user"
done
# Loop over arguments
for arg in "$@"; do
echo "Argument: $arg"
done# Basic while
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
((count++))
done
# Read file line by line
while IFS= read -r line; do
echo "Line: $line"
done < /etc/passwd
# Infinite loop
while true; do
echo "Press Ctrl+C to stop"
sleep 1
done
# While with condition
while ! ping -c1 -W1 google.com &>/dev/null; do
echo "Waiting for network..."
sleep 5
done
echo "Network is up!"# Runs UNTIL condition is true (opposite of while)
count=1
until [ $count -gt 5 ]; do
echo "Count: $count"
((count++))
done# break โ exit the loop
for i in {1..100}; do
if [ $i -eq 5 ]; then break; fi
echo "$i"
done
# Output: 1 2 3 4
# continue โ skip to next iteration
for i in {1..10}; do
if (( i % 2 == 0 )); then continue; fi
echo "$i"
done
# Output: 1 3 5 7 9#!/bin/bash
# Define a function
greet() {
echo "Hello, $1!"
}
# Call the function
greet "Sovon" # Hello, Sovon!
greet "Alice" # Hello, Alice!
# Function with return value
is_even() {
if (( $1 % 2 == 0 )); then
return 0 # True (success)
else
return 1 # False (failure)
fi
}
if is_even 42; then
echo "42 is even"
fi
# Function that outputs a value
get_hostname() {
echo $(hostname)
}
my_host=$(get_hostname)
# Function with local variables
calculate() {
local result=$(( $1 + $2 )) # 'local' keeps it inside function
echo $result
}
sum=$(calculate 10 20)
echo "Sum: $sum" # 30Every command returns an exit code (0 = success, 1-255 = failure).
# Check exit code
ls /tmp
echo $? # 0 (success)
ls /nonexistent
echo $? # 2 (failure)#!/bin/bash
# Exit on any error
set -e
# Exit on undefined variable
set -u
# Pipe fails if any command fails
set -o pipefail
# Common combination (put at the top of every script)
set -euo pipefail
# Custom error handling
trap 'echo "Error on line $LINENO"; exit 1' ERR
# Or handle cleanup on exit
cleanup() {
echo "Cleaning up temporary files..."
rm -f /tmp/mytemp.*
}
trap cleanup EXIT# && = run second command only if first succeeds
mkdir /tmp/test && echo "Directory created"
# || = run second command only if first fails
mkdir /tmp/test || echo "Failed to create directory"
# Combine for try-catch pattern
command_that_might_fail && echo "Success" || echo "Failed"# Declare an array
fruits=("apple" "banana" "cherry" "date")
# Access elements (0-indexed)
echo ${fruits[0]} # apple
echo ${fruits[2]} # cherry
# All elements
echo ${fruits[@]} # apple banana cherry date
# Number of elements
echo ${#fruits[@]} # 4
# Add element
fruits+=("elderberry")
# Remove element
unset fruits[1] # Remove banana
# Slice
echo ${fruits[@]:1:2} # Elements 1-2
# Loop over array
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
# Associative arrays (bash 4+)
declare -A colors
colors[red]="#FF0000"
colors[green]="#00FF00"
colors[blue]="#0000FF"
echo ${colors[red]} # #FF0000
echo ${!colors[@]} # Keys: red green blue
echo ${colors[@]} # Values: #FF0000 #00FF00 #0000FFstr="Hello, World!"
# Length
echo ${#str} # 13
# Substring
echo ${str:7} # World!
echo ${str:7:5} # World
# Replace
echo ${str/World/Linux} # Hello, Linux!
echo ${str//l/L} # HeLLo, WorLd! (replace all)
# Remove prefix/suffix
file="document.tar.gz"
echo ${file#*.} # tar.gz (remove shortest prefix match)
echo ${file##*.} # gz (remove longest prefix match)
echo ${file%.*} # document.tar (remove shortest suffix)
echo ${file%%.*} # document (remove longest suffix)
# Default values
echo ${var:-"default"} # Use "default" if var is unset
echo ${var:="default"} # Set var to "default" if unset
echo ${var:+"alternative"} # Use "alternative" if var IS set
echo ${var:?"Error msg"} # Print error and exit if unset
# Case conversion (bash 4+)
echo ${str^^} # HELLO, WORLD! (uppercase)
echo ${str,,} # hello, world! (lowercase)
echo ${str^} # Hello, World! (capitalize first)# Using $(( ))
echo $((5 + 3)) # 8
echo $((10 / 3)) # 3 (integer division)
echo $((10 % 3)) # 1 (modulo)
echo $((2 ** 10)) # 1024 (power)
# Increment/Decrement
((count++))
((count--))
((count += 5))
# Using let
let "result = 5 * 3"
echo $result # 15
# For floating point, use bc
echo "scale=2; 10 / 3" | bc # 3.33
echo "scale=4; 3.14 * 2" | bc # 6.28# Run with debug output
bash -x script.sh # Print each command before executing
# Enable debug mode inside script
set -x # Start debug
set +x # Stop debug
# Trace with line numbers
export PS4='+(${BASH_SOURCE}:${LINENO}): '
set -x
# Syntax check without running
bash -n script.sh # Check for syntax errors
# Use shellcheck for best practices
sudo apt install shellcheck
shellcheck script.sh # Lint your script- Hello Script: Write a script that greets the user by name (passed as argument)
- Calculator: Write a script that takes two numbers and an operator (+, -, *, /) and prints the result
- File Checker: Write a script that checks if a file exists, is readable, and shows its size
- Backup Script: Write a script that copies a directory to a backup location with a timestamp
- User Creator: Write a script that reads a list of usernames from a file and creates each user
- System Report: Write a script that outputs hostname, uptime, disk usage, and memory usage
- Log Analyzer: Write a script that counts error occurrences in
/var/log/syslog - Menu System: Create an interactive menu using
selectorcase
โ Previous: Text Editors ยท ๐ Home ยท Next: Process Management โ