Back to Blog
Linux

Linux Shell Scripting for Beginners: From Zero to Your First Script

Vajo Lukic
June 20, 2026
7 min read
Linux Shell Scripting for Beginners: From Zero to Your First Script

A shell script is a text file containing commands that your terminal would normally run one at a time. Scripting lets you automate repetitive tasks, combine commands in sequence, and add logic like conditions and loops.

If you can run commands in a terminal, you already know the basics — this guide shows you how to put them together.

Your First Shell Script

Create a file, give it the right permissions, and run it:

# Create the script
nano hello.sh
#!/bin/bash
echo "Hello, World!"
echo "Today is: $(date)"
# Make it executable
chmod +x hello.sh

# Run it
./hello.sh

Output:

Hello, World!
Today is: Fri Jun 20 14:23:11 UTC 2026

The shebang line

#!/bin/bash is the shebang — it tells the system which interpreter to use. Without it, the script might run with your current shell (which may work) or fail (which may not be obvious).

Common shebangs:

#!/bin/bash          # bash — most compatible
#!/usr/bin/env bash  # finds bash in PATH — more portable
#!/bin/sh            # POSIX sh — less features, more portable

Variables

#!/bin/bash

name="Alice"
count=42
greeting="Hello, $name!"

echo $name
echo $count
echo "$greeting, you have $count messages."

Rules:

  • No spaces around = in assignments: name="Alice" works, name = "Alice" fails
  • Access variables with $ prefix: $name or ${name}
  • Use double quotes to prevent word splitting: "$name" not $name when the value might have spaces

Command substitution

Capture command output into a variable:

current_date=$(date +%Y-%m-%d)
file_count=$(ls | wc -l)
hostname=$(hostname)

echo "Date: $current_date"
echo "Files: $file_count"
echo "Host: $hostname"

Special variables

Variable Value
$0 Script name
$1, $2, ... Positional arguments
$# Number of arguments
$@ All arguments as separate words
$* All arguments as one string
$? Exit code of the last command
$$ PID of the current script
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "All arguments: $@"
echo "Argument count: $#"

Run: ./script.sh foo bar baz

User Input

#!/bin/bash
echo "Enter your name:"
read name
echo "Hello, $name!"

# Read with a prompt on the same line
read -p "Enter filename: " filename
echo "You entered: $filename"

# Read silently (for passwords)
read -s -p "Password: " password
echo  # newline after silent input

Conditionals

if/else

#!/bin/bash

age=$1

if [ "$age" -ge 18 ]; then
    echo "Adult"
elif [ "$age" -ge 13 ]; then
    echo "Teenager"
else
    echo "Child"
fi

Comparison operators

Numeric comparisons (use inside [ ] or (( ))):

Operator Meaning
-eq equal
-ne not equal
-lt less than
-le less than or equal
-gt greater than
-ge greater than or equal

String comparisons:

Operator Meaning
= equal
!= not equal
-z empty string
-n non-empty string

File tests:

Operator Meaning
-f file exists and is a regular file
-d dir exists and is a directory
-e path exists (any type)
-r file exists and is readable
-w file exists and is writable
-x file exists and is executable
-s file exists and is non-empty
#!/bin/bash

if [ -f "/etc/nginx/nginx.conf" ]; then
    echo "nginx config exists"
fi

if [ ! -d "/tmp/backups" ]; then
    mkdir -p /tmp/backups
    echo "Created backup directory"
fi

Modern test syntax [[ ]]

[[ ]] is a bash extension that handles edge cases better than [ ]:

# Pattern matching
if [[ "$filename" == *.txt ]]; then
    echo "It's a text file"
fi

# Logical operators
if [[ "$status" == "active" && "$count" -gt 0 ]]; then
    echo "System is active with items"
fi

# Regex matching
if [[ "$input" =~ ^[0-9]+$ ]]; then
    echo "Input is a number"
fi

case statement

#!/bin/bash

case "$1" in
    start)
        echo "Starting service..."
        ;;
    stop)
        echo "Stopping service..."
        ;;
    restart)
        echo "Restarting service..."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac

Loops

for loop

#!/bin/bash

# Loop over a list
for fruit in apple banana cherry; do
    echo "Fruit: $fruit"
done

# Loop over files
for file in *.txt; do
    echo "Processing: $file"
    wc -l "$file"
done

# C-style for loop
for ((i=1; i<=5; i++)); do
    echo "Count: $i"
done

# Loop over command output
for user in $(cat /etc/passwd | cut -d: -f1); do
    echo "User: $user"
done

while loop

#!/bin/bash

count=1
while [ $count -le 5 ]; do
    echo "Count: $count"
    ((count++))
done

# Read a file line by line
while IFS= read -r line; do
    echo "Line: $line"
done < /etc/hosts

until loop

Runs until a condition becomes true (opposite of while):

until ping -c 1 google.com &>/dev/null; do
    echo "Waiting for network..."
    sleep 2
done
echo "Network is up"

Functions

#!/bin/bash

# Define a function
greet() {
    local name="$1"      # local: variable only exists inside the function
    echo "Hello, $name!"
}

# Call it
greet "Alice"
greet "Bob"

# Return values
get_file_size() {
    local file="$1"
    if [ -f "$file" ]; then
        wc -c < "$file"
    else
        echo 0
    fi
}

size=$(get_file_size /etc/hosts)
echo "File size: $size bytes"

Functions return exit codes (0 = success, non-zero = failure), not values. To return a value, use echo and capture with $().

Exit Codes and Error Handling

Every command exits with a code. 0 means success; anything else means failure.

#!/bin/bash

# Check if a command succeeded
if cp important.txt /backup/; then
    echo "Backup succeeded"
else
    echo "Backup failed!"
    exit 1
fi

# Or use $? directly
ls /nonexistent
echo "Exit code: $?"    # prints 2 (file not found)

set -e: exit on error

#!/bin/bash
set -e    # exit the script if any command fails

cp file1.txt /backup/
cp file2.txt /backup/    # if this fails, script stops here
echo "All backed up"     # only runs if both copies succeeded

set -u: fail on undefined variables

#!/bin/bash
set -u    # treat unset variables as errors

echo $undefined_var    # would normally print nothing; now exits with error

Common to see both together: set -eu or set -euo pipefail.

Practical Script Examples

Backup script

#!/bin/bash
set -euo pipefail

BACKUP_DIR="/backup/$(date +%Y-%m-%d)"
SOURCE_DIR="/var/www/html"

mkdir -p "$BACKUP_DIR"
tar czf "$BACKUP_DIR/website.tar.gz" "$SOURCE_DIR"
echo "Backup complete: $BACKUP_DIR/website.tar.gz"

Check if a service is running

#!/bin/bash

SERVICE="nginx"

if systemctl is-active --quiet "$SERVICE"; then
    echo "$SERVICE is running"
else
    echo "$SERVICE is NOT running — starting it..."
    sudo systemctl start "$SERVICE"
fi

Process a list of files

#!/bin/bash

INPUT_DIR="./reports"
OUTPUT_DIR="./processed"

mkdir -p "$OUTPUT_DIR"

for file in "$INPUT_DIR"/*.csv; do
    filename=$(basename "$file")
    echo "Processing $filename..."
    # example: convert to uppercase
    tr '[:lower:]' '[:upper:]' < "$file" > "$OUTPUT_DIR/$filename"
done

echo "Done. Processed files are in $OUTPUT_DIR/"

Debugging Scripts

bash -x script.sh       # print each command before executing
bash -n script.sh       # check syntax without running
set -x                  # enable in the script itself
set +x                  # disable tracing

With bash -x, you see each expanded command before it runs — invaluable for tracking down where things go wrong.

Next Steps

This covers the core of shell scripting. From here, explore:

  • sed and awk for text processing
  • cron for scheduling scripts
  • trap for cleanup on exit
  • Arrays and associative arrays (bash 4+)

The Linux Shell Scripting Basics Guide covers these topics with additional real-world automation examples.

Get The Practical Linux Handbook
Read a free sample
All Linux topics

#linux#shell-scripting#bash#automation#scripting#beginners#terminal

Enjoyed this article? Share it!

About the Author

VL

Vajo Lukic

Vajo Lukic is a technology leader with 20+ years of experience in software development and system administration. Author of The Practical Linux Handbook, he shares practical, field-tested knowledge to help developers and IT professionals master Linux fundamentals.

Read more about Vajo

Related Articles

Linux File Permissions: The Complete Beginner's Guide (chmod, chown, umask)

Linux File Permissions: The Complete Beginner's Guide (chmod, chown, umask)

Learn Linux file permissions from scratch. Understand rwx, chmod numbers, chown, and umask with clear examples that make the permission system click.

Read more →
SSH Remote Access: Step-by-Step Guide for Linux Beginners

SSH Remote Access: Step-by-Step Guide for Linux Beginners

Learn to connect to Linux systems remotely with SSH. From your first ssh command to key-based authentication, port forwarding, and secure configuration.

Read more →
10 Essential Linux Commands Every Developer Should Know

10 Essential Linux Commands Every Developer Should Know

Learn these 10 fundamental Linux commands and transform your productivity. From file navigation to system monitoring, discover the tools that every developer needs in their arsenal.

Read more →

Ready to Transform Your Life?

Get the complete guide to personal transformation and start your journey today.

Get the Book