</>
CodeLearn Pro
Start Learning Free โ†’

Bash โ€” Complete Beginner Tutorial

6 modules ยท 18 examples ยท Click โ–ถ Run Script to see terminal output

โœฆ Beginner
โ† Overview

๐Ÿ’ก How to use this page

Read the explanation, study the code, then hit โ–ถ Run Script to see the output.

Module 01

Terminal & Basic Commands

The terminal is a text interface where you type commands and the computer responds. Before writing Bash scripts, you need to know the essential commands for moving around your file system and working with files. Think of the terminal as a very precise, very fast way to talk to your computer.

Navigating Your File System

The file system is like a tree of folders. pwd shows where you currently are. ls lists what is in the current folder. cd moves you into a different folder. These three commands are the foundation of everything you do in the terminal.

bash โ€” example.shBash
# pwd โ€” Print Working Directory (where am I right now?)
pwd

# ls โ€” List files and folders here
ls

# ls -l โ€” Long format: shows permissions, size, date
ls -l

# ls -la โ€” Also show hidden files (files starting with a dot)
ls -la

# cd โ€” Change Directory (move into a folder)
cd Documents

# cd .. โ€” Go UP one level (to the parent folder)
cd ..

# cd ~ โ€” Go to your home directory (always works!)
cd ~

# cd /var/log โ€” Go to an absolute path
cd /var/log
$ bash example.sh
๐Ÿ’ก

~ is a shortcut for your home directory (/home/yourname on Linux, /Users/yourname on Mac). You can always get back to safety with cd ~ no matter how deep in folders you are.

Creating, Copying & Deleting

mkdir creates a new folder. touch creates an empty file. cp copies files or folders. mv moves or renames them. rm deletes files โ€” permanently, with no recycle bin! Always double-check before using rm.

bash โ€” example.shBash
# mkdir โ€” Make a new directory (folder)
mkdir my_project
mkdir -p projects/2025/april   # -p creates parent folders too

# touch โ€” Create an empty file
touch hello.sh
touch notes.txt config.json

# cp โ€” Copy a file
cp hello.sh hello_backup.sh

# cp -r โ€” Copy a whole folder (recursively)
cp -r my_project my_project_backup

# mv โ€” Move or rename a file
mv notes.txt Documents/notes.txt    # move to folder
mv hello.sh greet.sh                # rename

# rm โ€” Remove (delete) a file โ€” NO UNDO!
rm old_file.txt

# rm -r โ€” Remove a whole folder and everything in it
rm -r my_project_backup

# Echo the result
echo "Files in current directory:"
ls
$ bash example.sh
๐Ÿ’ก

โš ๏ธ rm is permanent โ€” there is no recycle bin in the terminal. rm -rf (recursive + force) is one of the most dangerous commands on a computer. Always double-check your path before running it.

Reading & Searching Files

cat prints the entire contents of a file. less lets you scroll through long files. head shows the first 10 lines. tail shows the last 10 โ€” great for watching log files. grep searches for text inside files.

bash โ€” example.shBash
# cat โ€” Print the whole file to the terminal
cat notes.txt

# head โ€” Show first 10 lines
head server.log

# head -n 5 โ€” Show first 5 lines
head -n 5 server.log

# tail โ€” Show last 10 lines
tail server.log

# tail -f โ€” Follow the file live (great for logs!)
# tail -f /var/log/syslog

# grep โ€” Search for a pattern in a file
grep "error" server.log

# grep -i โ€” Case-insensitive search
grep -i "ERROR" server.log

# grep -n โ€” Show line numbers
grep -n "Alice" users.txt

# wc -l โ€” Count lines in a file
wc -l server.log
$ bash example.sh
๐Ÿ’ก

tail -f is your best friend for monitoring servers. It keeps printing new lines as they are added to the file โ€” so you can watch a log update in real time.

Module 02

Variables & Input

Variables in Bash store text and numbers so you can reuse them. There are no types โ€” everything is text by default. You use a $ to read a variable's value. Bash also has special built-in variables and a way to capture the output of commands into variables.

Declaring & Using Variables

Assign a variable with name=value โ€” NO spaces around the equals sign (this is a very common beginner mistake). Read it back with $name or ${name}. Use double quotes around variable expansions to handle spaces safely.

bash โ€” example.shBash
#!/bin/bash

# Assign variables โ€” NO spaces around =
name="Alice"
age=25
city="Cape Town"
pi=3.14

# Read variables with $
echo $name
echo $age

# Better: use double quotes (handles spaces in values)
echo "Hello, $name!"
echo "You are $age years old."

# Curly braces: useful when variable name is next to text
version=3
echo "Python${version} is great"
echo "Python$version is great"

# Maths with $(( ))
result=$((age + 5))
echo "In 5 years: $result"

score=80
percentage=$((score * 100 / 100))
echo "Score: $percentage%"
$ bash example.sh
๐Ÿ’ก

NEVER put spaces around the = when assigning a variable. name = "Alice" causes an error because Bash thinks "name" is a command. name="Alice" is correct.

Special Variables & Command Substitution

Bash has built-in variables that are always available: $0 is the script name, $1 $2... are arguments passed to the script, $# is how many arguments, $? is the exit code of the last command. Command substitution $(command) runs a command and uses its output as a value.

bash โ€” example.shBash
#!/bin/bash

# Special built-in variables
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Number of arguments: $#"
echo "All arguments: $@"

# $? โ€” exit code of the last command (0 = success)
ls /home
echo "Exit code of ls: $?"

ls /nonexistent_folder 2>/dev/null
echo "Exit code of failed ls: $?"

# Command substitution โ€” capture output of a command
current_date=$(date +"%Y-%m-%d")
echo "Today is: $current_date"

current_user=$(whoami)
echo "Running as: $current_user"

file_count=$(ls | wc -l)
echo "Files in folder: $file_count"

# Useful: hostname
machine=$(hostname)
echo "Machine: $machine"
$ bash example.sh
๐Ÿ’ก

Exit code 0 always means success in Unix. Any non-zero code means something went wrong. This is the opposite of most programming languages! Checking $? after important commands is how you detect failures in scripts.

Reading User Input with read

The read command pauses the script and waits for the user to type something. The -p flag lets you show a prompt on the same line. The -s flag hides input โ€” perfect for passwords.

bash โ€” example.shBash
#!/bin/bash

# Basic input
read -p "What is your name? " name
echo "Hello, $name!"

# Input with default value
read -p "Enter your city (default: Cape Town): " city
city=${city:-"Cape Town"}
echo "City: $city"

# Hidden input (for passwords)
read -s -p "Enter your password: " password
echo ""
echo "Password length: ${#password} characters"

# Read multiple values at once
read -p "Enter first and last name: " first last
echo "First: $first"
echo "Last:  $last"

# Read into an array
read -p "Enter three colours: " -a colours
echo "You chose: ${colours[0]}, ${colours[1]}, ${colours[2]}"
$ bash example.sh
๐Ÿ’ก

${variable:-default} is a Bash "default value" expansion. If the variable is empty or unset, it uses the default value. This is very handy for making scripts that work with or without user input.

Module 03

Conditions & Tests

Bash conditions work differently from other languages. You use square brackets [ ] to test things, and Bash has special operators for comparing numbers (-eq, -gt, -lt) and for testing files (-f, -d, -e). The syntax looks strange at first but becomes natural quickly.

if / elif / else

The if statement runs code when a condition is true. Note the spacing rules: spaces INSIDE the brackets are required. The condition ends with then, and the block ends with fi (if backwards). elif adds extra branches.

bash โ€” example.shBash
#!/bin/bash

score=75

# Basic if/elif/else
if [ $score -ge 80 ]; then
    echo "Grade: A โ€” Excellent!"
elif [ $score -ge 70 ]; then
    echo "Grade: B โ€” Good job!"
elif [ $score -ge 60 ]; then
    echo "Grade: C โ€” Average"
elif [ $score -ge 50 ]; then
    echo "Grade: D โ€” Just passed"
else
    echo "Grade: F โ€” Try again"
fi

# Comparing strings
day="Monday"

if [ "$day" = "Saturday" ] || [ "$day" = "Sunday" ]; then
    echo "It is the weekend!"
else
    echo "$day is a weekday."
fi

# Checking if a variable is empty
username=""

if [ -z "$username" ]; then
    echo "No username provided"
else
    echo "Username: $username"
fi
$ bash example.sh
๐Ÿ’ก

Spaces inside [ ] are REQUIRED. [ $x -eq 5 ] works. [$x -eq 5] causes an error. Also always quote string variables: [ "$name" = "Alice" ] โ€” without quotes, empty variables break the condition.

File Tests โ€” Checking Files & Folders

Bash has special operators for testing files. -e checks if something exists, -f checks if it is a regular file, -d checks if it is a directory, -r/-w/-x check permissions. These are used constantly in real scripts.

bash โ€” example.shBash
#!/bin/bash

file="notes.txt"
folder="Documents"

# -e  exists (file or directory)
if [ -e "$file" ]; then
    echo "$file exists"
else
    echo "$file does NOT exist"
fi

# -f  is a regular file (not a directory)
if [ -f "$file" ]; then
    echo "$file is a file"
fi

# -d  is a directory
if [ -d "$folder" ]; then
    echo "$folder is a directory"
fi

# -r  is readable
if [ -r "$file" ]; then
    echo "$file is readable"
fi

# -w  is writable
if [ -w "$file" ]; then
    echo "$file is writable"
fi

# -s  is not empty (has content)
if [ -s "$file" ]; then
    echo "$file has content"
else
    echo "$file is empty"
fi

# Practical: create folder only if it doesn't exist
backup_dir="./backups"
if [ ! -d "$backup_dir" ]; then
    mkdir "$backup_dir"
    echo "Created $backup_dir"
else
    echo "$backup_dir already exists"
fi
$ bash example.sh
๐Ÿ’ก

! flips any test: [ ! -f "$file" ] means "file does NOT exist". This is used constantly โ€” "if the config file does not exist, create a default one" is a very common script pattern.

case โ€” Multiple Options

The case statement is like a cleaner version of many if/elif branches. It matches a value against patterns and runs the matching block. Each option ends with ;; and the whole case ends with esac (case backwards).

bash โ€” example.shBash
#!/bin/bash

read -p "Enter a day (Mon/Tue/Wed...): " day

case "$day" in
    Mon|Monday)
        echo "Start of the work week. Let's go!"
        ;;
    Tue|Tuesday)
        echo "Tuesday โ€” keep the momentum."
        ;;
    Wed|Wednesday)
        echo "Midweek already!"
        ;;
    Thu|Thursday)
        echo "Nearly Friday..."
        ;;
    Fri|Friday)
        echo "TGIF! Almost weekend."
        ;;
    Sat|Saturday|Sun|Sunday)
        echo "Weekend! Time to rest."
        ;;
    *)
        echo "Unknown day: $day"
        ;;
esac

# Case with commands
action="backup"

case "$action" in
    start)   echo "Starting service..." ;;
    stop)    echo "Stopping service..." ;;
    restart) echo "Restarting service..." ;;
    backup)  echo "Running backup now..." ;;
    *)       echo "Unknown action: $action" ;;
esac
$ bash example.sh
๐Ÿ’ก

The * pattern at the end of a case is the "catch-all" โ€” it matches anything that did not match the earlier patterns. Always include it as your last option to handle unexpected input gracefully.

Module 04

Loops

Loops in Bash let you repeat commands โ€” process every file in a folder, count through numbers, or keep asking for input until you get a valid answer. The for loop and while loop cover 95% of real use cases.

for Loops

Bash for loops work over a list of items. You can list items directly, use a range with {1..10}, use seq for more control, or loop over files with a wildcard pattern. The C-style for loop also works if you prefer it.

bash โ€” example.shBash
#!/bin/bash

# Loop over a list of items
for colour in red green blue yellow; do
    echo "Colour: $colour"
done

echo "---"

# Loop with a number range {start..end}
for i in {1..5}; do
    echo "Count: $i"
done

echo "---"

# Loop with step {start..end..step}
for i in {0..20..5}; do
    echo -n "$i "
done
echo

echo "---"

# C-style for loop
for (( i=1; i<=4; i++ )); do
    echo "Item $i"
done

echo "---"

# Loop over all .txt files in a directory
for file in *.txt; do
    echo "Found text file: $file"
done
$ bash example.sh
๐Ÿ’ก

Looping over files with *.txt is incredibly useful. You can rename all files at once, process every log, convert every image. This is where Bash automation really shines.

while Loops & break / continue

A while loop runs as long as a condition is true. It is great for reading files line by line, waiting for something to happen, or repeating until the user gives valid input. break exits the loop early, continue skips to the next iteration.

bash โ€” example.shBash
#!/bin/bash

# Basic while loop โ€” countdown
count=5
while [ $count -gt 0 ]; do
    echo "$count..."
    count=$((count - 1))
done
echo "Blast off!"

echo "---"

# Validate user input with a loop
while true; do
    read -p "Enter a number between 1-10: " num
    if [ "$num" -ge 1 ] && [ "$num" -le 10 ] 2>/dev/null; then
        echo "Great! You entered: $num"
        break
    else
        echo "Invalid! Please try again."
    fi
done

echo "---"

# continue โ€” skip even numbers
for i in {1..8}; do
    remainder=$((i % 2))
    if [ $remainder -eq 0 ]; then
        continue
    fi
    echo "Odd: $i"
done
$ bash example.sh
๐Ÿ’ก

while true; do ... done creates an infinite loop that only stops when you hit break. This pattern is perfect for menus and input validation โ€” keep asking until you get something valid.

Module 05

Functions & Scripts

Functions let you group commands under a name and call them multiple times. Arguments are passed positionally ($1, $2...) just like script arguments. Scripts are just text files with a .sh extension and a special first line called a shebang.

Defining & Calling Functions

Define a function with the function name or just name(). Put the commands inside curly braces. Call it by just typing its name. Bash functions must be defined BEFORE they are called โ€” put them at the top of your script.

bash โ€” example.shBash
#!/bin/bash

# Define a function
greet() {
    echo "Hello, $1!"
    echo "Welcome to the terminal."
}

# Call the function with an argument
greet "Alice"
greet "Bob"

echo "---"

# Function with multiple arguments
introduce() {
    local name=$1
    local age=$2
    local city=$3
    echo "$name is $age years old and lives in $city."
}

introduce "Alice" 25 "Cape Town"
introduce "Bob"   30 "Johannesburg"

echo "---"

# Function that returns a value via echo
add_numbers() {
    local result=$(( $1 + $2 ))
    echo $result
}

sum=$(add_numbers 15 27)
echo "15 + 27 = $sum"

double=$(add_numbers $sum $sum)
echo "Doubled: $double"
$ bash example.sh
๐Ÿ’ก

Always use local for variables inside functions. Without local, variables are global and can accidentally overwrite variables in other parts of your script โ€” a very hard bug to track down.

Writing a Complete Script

A proper Bash script starts with a shebang line (#!/bin/bash), has a description comment, defines functions near the top, and then runs the main logic at the bottom. Make it executable with chmod +x and run it with ./scriptname.sh.

bash โ€” example.shBash
#!/bin/bash
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# backup.sh โ€” Simple backup script
# Usage: ./backup.sh <source_folder>
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

# --- Configuration ---
BACKUP_DIR="$HOME/backups"
DATE=$(date +"%Y-%m-%d_%H-%M")

# --- Functions ---

check_args() {
    if [ $# -lt 1 ]; then
        echo "Usage: $0 <folder_to_backup>"
        exit 1
    fi
}

create_backup_dir() {
    if [ ! -d "$BACKUP_DIR" ]; then
        mkdir -p "$BACKUP_DIR"
        echo "Created backup directory: $BACKUP_DIR"
    fi
}

do_backup() {
    local source=$1
    local dest="$BACKUP_DIR/backup_${DATE}.tar.gz"

    echo "Backing up: $source"
    echo "Destination: $dest"

    tar -czf "$dest" "$source" 2>/dev/null
    echo "Backup complete!"
    echo "Size: $(du -sh "$dest" | cut -f1)"
}

# --- Main ---
check_args "$@"
create_backup_dir
do_backup "$1"
$ bash example.sh
๐Ÿ’ก

Make a script executable with: chmod +x backup.sh โ€” then run it with: ./backup.sh. The shebang #!/bin/bash on line 1 tells the system which interpreter to use. Without it, the script might run with the wrong shell.

Module 06

Files & Text Processing

Bash is incredibly powerful for processing text files. Pipes (|) chain commands together so the output of one becomes the input of the next. grep, sed, and awk are three classic tools that together can transform, search, and analyse almost any text data.

Pipes & Redirection

The pipe | sends output from one command into another. > writes output to a file (overwrites). >> appends to a file. < reads input from a file. 2> redirects errors. These are the plumbing of the Unix world.

bash โ€” example.shBash
#!/bin/bash

# | pipe โ€” chain commands
# Count how many files are in the folder
ls | wc -l

# Find all .sh files and count them
ls *.sh | wc -l

# Sort the output of ls alphabetically
ls | sort

# Search and count matching lines
grep "error" server.log | wc -l

# Chain multiple pipes
cat server.log | grep "ERROR" | sort | uniq

echo "---"

# > Redirect output to a file (creates or overwrites)
echo "Log started" > output.log
date >> output.log
ls -la >> output.log

# Read file contents
cat output.log

echo "---"

# 2> Redirect errors to a file (suppress them)
ls /nonexistent 2>/dev/null
ls /nonexistent 2>errors.log

# Redirect both output AND errors
ls /home /nonexistent > all.log 2>&1
$ bash example.sh
๐Ÿ’ก

2>/dev/null is a very common pattern โ€” it sends error messages into "the void" so they do not clutter your output. The /dev/null file is a special file that discards everything written to it.

grep & sed โ€” Search & Replace

grep is the go-to tool for searching text. sed is the "stream editor" โ€” it finds and replaces text in files or streams without opening them in an editor. Together they handle most text processing tasks.

bash โ€” example.shBash
#!/bin/bash

# โ”€โ”€ GREP โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Basic search
grep "Alice" users.txt

# -i  Case insensitive
grep -i "alice" users.txt

# -n  Show line numbers
grep -n "error" server.log

# -c  Count matching lines only
grep -c "error" server.log

# -v  Invert โ€” show NON-matching lines
grep -v "INFO" server.log

# -r  Search recursively in a folder
grep -r "TODO" ./scripts/

# โ”€โ”€ SED โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Replace first occurrence per line
sed 's/error/ERROR/' server.log

# Replace ALL occurrences per line (g flag)
sed 's/http/https/g' urls.txt

# Edit file in place (saves changes to file)
sed -i 's/localhost/production.server.com/g' config.txt

# Delete lines matching a pattern
sed '/^#/d' config.txt

# Print only specific lines (e.g. lines 5-10)
sed -n '5,10p' bigfile.txt
$ bash example.sh
๐Ÿ’ก

sed 's/old/new/g' is the most used sed pattern. The s stands for substitute, the g at the end means global (all occurrences on each line). Without the g it only replaces the first match per line.

File Permissions & chmod

Every file on Linux has permissions for three groups: owner, group, and others. chmod changes these permissions. The +x flag makes a file executable โ€” required before you can run a script.

bash โ€” example.shBash
#!/bin/bash

# View permissions with ls -l
ls -l myscript.sh

# chmod โ€” Change file permissions
# Using letters:
chmod +x myscript.sh
chmod +r notes.txt
chmod -w readonly.txt
chmod u+x script.sh
chmod go-w private.sh

# Using numbers (octal):
# 4=read  2=write  1=execute
chmod 755 myscript.sh   # rwxr-xr-x
chmod 644 config.txt    # rw-r--r--
chmod 600 secret.key    # rw-------

ls -l myscript.sh
echo "Script is now executable!"
$ bash example.sh
๐Ÿ’ก

755 is the standard permission for scripts and programs. 644 is standard for regular files. 600 for anything sensitive like SSH keys or passwords.

You finished the Bash tutorial!

You can now navigate the terminal, write scripts with variables, conditions, loops and functions, and process files with grep, sed and pipes. These skills power DevOps, Linux admin, and automation worldwide.