Shell Script Tutorial for Beginners - Bash Variables, If, and Loops

Shell Script Tutorial for Beginners - Bash Variables, If, and Loops

What you'll be able to do

  • Create and run your own scripts with a shebang and execute permission
  • Automate work by combining variables, conditionals, and loops
  • Isolate and fix the causes of common syntax errors

Prerequisites (read these first)

A shell script is a file that collects the commands you would type in a terminal so they can be run together. "Shell script", "bash script", and "shell program" all mean the same thing.

Put work you used to type by hand into one file, and next time a single run does it for you. This basics edition covers Bash syntax from variables and conditionals to loops.

What You'll Learn

  • You will be able to create and run your own scripts, understanding the shebang line and execute permission.
  • You will be able to automate repetitive work by combining variables, conditionals, and loops.
  • You will be able to isolate and fix the causes of the most common syntax errors.

Who this is for: anyone who has typed commands in a terminal and wants to automate work they keep repeating.

Prerequisites: this article reads variables such as $HOME. If that is new to you, read Environment Variables Basics first.

Terms defined up front

Each term below is defined once, here.

  • A shell is the program that receives the commands you type and runs them. On Linux, Bash is the default.
  • A shebang is the #!/bin/bash line at the top of a script. It is also called a hashbang. It declares which shell should run the file.
  • Execute permission is the permission that allows a file to be launched as a program. A script without it stops with Permission denied.
  • A variable is a box holding a value under a name. name="value" puts a value in; $name takes it out.
  • Quotes are " and '. If a value contains spaces, the shell reads it as separate words unless you quote it.
  • The exit status is the number from 0 to 255 a command returns when it finishes. 0 means success and anything else means failure. It is also called the exit code or return code.
  • Command substitution is the form $(date), which embeds the output of a command in place.
  • Arithmetic expansion is the form $((1 + 2)), which embeds a calculated result. Left alone, the shell treats numbers as text, so calculations need this form. It handles integers only, and fractional parts are discarded.

Your First Shell Script

Conclusion: #!/bin/bash, chmod +x, ./script.sh — three steps that start every Bash script.

Basic Structure

#!/bin/bash
# This is a comment

echo "Hello, Shell Script!"
echo "Current date/time: $(date)"
echo "Username: $USER"

Creating and Running Scripts

Step 1: Create File

$ nano hello.sh

Enter the above code and save.

Step 2: Add Execute Permission

$ chmod +x hello.sh

Step 3: Run Script

$ ./hello.sh
Hello, Shell Script!
Current date/time: Sat Jan 11 14:30:00 JST 2025
Username: user

The format date prints depends on the locale. Under a Japanese locale (ja_JP.UTF-8), for instance, it comes out as 2025年 1月11日 土曜日 14時30分00秒 JST.

The ./ in ./hello.sh means "in the directory I am in". Drop it and the shell searches only PATH, which ends in command not found.

About Shebang

#!/bin/bash is called a shebang and specifies the interpreter — the program responsible for running the file.

  • #!/bin/bash — Use Bash
  • #!/bin/sh — Use POSIX shell
  • #!/usr/bin/env bash — Auto-detect from environment

Forget the shebang and behaviour depends on which shell happens to run the file. With #!/bin/sh in particular, Bash-only syntax such as [[ ]] and arrays is unavailable. When in doubt, write #!/bin/bash.

Variables and Input

Conclusion: No spaces around =; quote all uses; use $() for substitution; read -p for input.

Variable Basics

#!/bin/bash

# Define variables (no spaces around =)
name="Linux User"
age=25
today=$(date +%Y-%m-%d)

# Use variables
echo "Name: $name"
echo "Age: ${age} years old"
echo "Today's date: $today"

Calculation Example

#!/bin/bash

num1=10
num2=3

sum=$((num1 + num2))
diff=$((num1 - num2))
product=$((num1 * num2))
quotient=$((num1 / num2))

echo "Addition: $num1 + $num2 = $sum"
echo "Subtraction: $num1 - $num2 = $diff"
echo "Multiplication: $num1 × $num2 = $product"
echo "Division: $num1 ÷ $num2 = $quotient"
Addition: 10 + 3 = 13
Subtraction: 10 - 3 = 7
Multiplication: 10 × 3 = 30
Division: 10 ÷ 3 = 3

The division result of 3 is not a mistake. Arithmetic expansion in the shell is integer arithmetic, and the fractional part is discarded ($((10 / 3)) is 3). When you need decimals, use bc or awk.

echo "scale=2; 10/3" | bc      # 3.33
awk 'BEGIN {print 10/3}'       # 3.33333

Special Variables

Variable Description Example
$0 Script name ./script.sh
$1, $2, ... Command line arguments 1st arg, 2nd arg
$# Number of arguments 3 for 3 arguments
$@ All arguments "arg1" "arg2" "arg3"
$? Exit status of last command 0 for success, non-0 for failure
$$ Current process ID 12345
$USER Current username user
$HOME Home directory /home/user
$PWD Current directory /home/user/scripts

User Input

Basic Input

#!/bin/bash

echo "Please enter your name:"
read name
echo "Hello, ${name}!"

Input with Prompt

#!/bin/bash

read -p "Enter your age: " age
read -s -p "Enter password: " password
echo
echo "Age: $age"
echo "Password entered silently"

Multiple Values at Once

#!/bin/bash

echo "Enter name and age (space-separated):"
read name age
echo "Name: $name, Age: $age"

Conditionals

Conclusion: Spaces inside [ ] required; quote string variables; use -eq/-gt/-lt for numbers.

if Statement

Basic if Statement

#!/bin/bash

read -p "Enter a number: " num

if [ "$num" -gt 0 ]; then
    echo "$num is positive"
elif [ "$num" -lt 0 ]; then
    echo "$num is negative"
else
    echo "$num is zero"
fi

File Existence Check

#!/bin/bash

filename="test.txt"

if [ -f "$filename" ]; then
    echo "$filename exists"
    echo "File size: $(wc -c < "$filename") bytes"
else
    echo "$filename does not exist"
    echo "Creating file..."
    touch "$filename"
fi

Conditional Operators

Numeric Comparison

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 ]

String Comparison

Operator Meaning Example
= Equal [ "$a" = "$b" ]
!= Not equal [ "$a" != "$b" ]
-z Empty string [ -z "$str" ]
-n Not empty [ -n "$str" ]

File Tests

Operator Meaning Example
-f Regular file [ -f file.txt ]
-d Directory [ -d /home ]
-e Exists [ -e path ]
-r Readable [ -r file ]
-w Writable [ -w file ]
-x Executable [ -x script ]

case Statement

#!/bin/bash

echo "Select an option:"
echo "1) List files"
echo "2) Show current time"
echo "3) Show system info"
echo "4) Exit"

read -p "Choice (1-4): " choice

case $choice in
    1)
        echo "=== File List ==="
        ls -la
        ;;
    2)
        echo "=== Current Time ==="
        date
        ;;
    3)
        echo "=== System Info ==="
        uname -a
        ;;
    4)
        echo "Exiting."
        exit 0
        ;;
    *)
        echo "Invalid choice."
        ;;
esac

Loops

Conclusion: {1..N} for ranges, while IFS= read for file lines, until for inverse conditions.

for Loop

Basic for Loop

#!/bin/bash

# Numeric range
for i in {1..5}
do
    echo "Count: $i"
done

echo "---"

# File processing
for file in *.txt
do
    echo "Processing: $file"
    wc -l "$file"
done

Loop with Arrays

#!/bin/bash

fruits=("apple" "banana" "orange" "grape")

echo "Fruit list:"
for fruit in "${fruits[@]}"
do
    echo "- $fruit"
done

C-style for Loop

#!/bin/bash

echo "Multiplication table (partial):"
for ((i=1; i<=5; i++))
do
    for ((j=1; j<=5; j++))
    do
        result=$((i * j))
        printf "%2d " $result
    done
    echo
done

while Loop

Basic while Loop

#!/bin/bash

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

Reading Files

#!/bin/bash

filename="data.txt"

if [ -f "$filename" ]; then
    while IFS= read -r line
    do
        echo "Read: $line"
    done < "$filename"
else
    echo "File $filename not found"
fi

until Loop

#!/bin/bash

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

Loop Control

  • break — Exit loop
  • continue — Skip to next iteration
#!/bin/bash

for i in {1..10}
do
    if [ $i -eq 3 ]; then
        echo "Skipping 3"
        continue
    fi

    if [ $i -eq 8 ]; then
        echo "Stopping at 8"
        break
    fi

    echo "Number: $i"
done

Common Beginner Mistakes and Pitfalls

Conclusion: Six traps in Bash scripts: spacing, braces, brackets, quotes, backticks, math.

Mistake 1: Spaces in Variable Assignment

NG (causes error)

name = "Taro"     # Space before/after =
age= 25          # Space after =
city ="Tokyo"     # Space before =

Results in "command not found" error.

OK (correct usage)

name="Taro"      # No spaces around =
age=25
city="Tokyo"

Variable assignment must have no spaces around =.

Mistake 2: Missing Braces in Variable Reference

NG (unexpected results)

filename="test"
echo "$filenameback.txt"    # prints just ".txt" ($filenameback is undefined)
echo "$filename_backup"     # prints nothing ($filename_backup is undefined)

The shell reads $filenameback as one variable name. It never splits it into $filename plus back.

OK (correct usage)

filename="test"
echo "${filename}back.txt"   # testback.txt
echo "${filename}_backup"    # test_backup

Use {} to clearly define variable name scope.

Mistake 3: Missing Spaces in if Statement Conditions

NG (syntax error)

if [$num -gt 5]; then       # No space after [
if [ $num -gt 5]; then      # No space before ]

OK (correct usage)

if [ $num -gt 5 ]; then     # Spaces inside [ ] required
if [[ $num -gt 5 ]]; then   # Same for [[ ]]
if (( num > 5 )); then      # Arithmetic expression

Mistake 4: Missing Quotes in String Comparison

NG (dangerous examples)

if [ $name = John Doe ]; then    # Issues with spaces
if [ $empty_var = "" ]; then     # Error when empty

OK (safe usage)

if [ "$name" = "John Doe" ]; then    # Quote both sides
if [ "$empty_var" = "" ]; then       # No error when empty
if [ -z "$var" ]; then               # Dedicated empty check option

Mistake 5: Old Command Substitution Syntax

NG (not recommended)

date=`date`                 # Backticks
result=`cat `which ls``     # Nesting causes error

OK (modern syntax)

date=$(date)                # Use $()
result=$(cat $(which ls))   # Easy to nest

Mistake 6: Arithmetic Operation Errors

NG (not calculated)

result = $num1 + $num2      # Interpreted as a "result" command (command not found)
sum="$a + $b"               # Not calculated

OK (correct calculation methods)

result=$((num1 + num2))     # Arithmetic expansion
let "result = num1 + num2"  # Use let command

Basic Rules to Prevent Mistakes

Writing Basics

  • Variable assignment: no spaces around =
  • Variable usage: always quote "$var"
  • Complex variables: use braces "${var}"

Conditional Basics

  • Using [ ]: always include internal spaces
  • String comparison: quote both sides
  • Numeric comparison: use -eq, -gt, -lt, etc.

Debugging and Troubleshooting

When a script misbehaves, do not guess — let set -x print the execution trace. Seeing how each variable expanded, line by line, makes the cause easy to spot.

set -x: print the execution trace

#!/bin/bash
set -x              # display every command from here on

filename="data.txt"
echo "DEBUG: filename = [$filename]"

if [ ! -f "$filename" ]; then
    echo "ERROR: File $filename not found" >&2
    exit 1
fi
+ filename=data.txt
+ echo 'DEBUG: filename = [data.txt]'
DEBUG: filename = [data.txt]
+ '[' '!' -f data.txt ']'
+ echo 'ERROR: File data.txt not found'
ERROR: File data.txt not found
+ exit 1

Lines starting with + are the commands the shell actually ran. Because you see them after variable expansion, causes such as "the variable was empty" or "the quotes did not apply" become identifiable on the spot. The value is printed inside [ ] to tell an empty string apart from a single space.

set -e / set -u: stop trouble early

#!/bin/bash
set -e              # stop as soon as a command fails
set -u              # stop as soon as an undefined variable is referenced

These two exist to stop processing from continuing past a problem unnoticed. set -u is especially effective at catching typos in variable names.

In a script with set -u enabled, referencing an undefined variable stops execution on that line with an unbound variable error. That is the intended behaviour, not a fault in set -x.

script.sh: line 5: var: unbound variable

Where you do not want it to stop, supply a default such as ${var:-}, which treats an undefined variable as an empty string.

Common Error Messages and Solutions

Symptom: command not found

Cause: spaces around = in a variable assignment, or running the script without ./.

Check:

bash -n script.sh    # syntax check only (does not run)

Fix: remove the spaces around =. Invoke the script as ./script.sh.

Symptom: Permission denied

Cause: the script has no execute permission.

Check:

ls -l script.sh

No x in something like -rw-r--r-- means no execute permission.

Fix:

chmod +x script.sh

Symptom: syntax error near unexpected token

Cause: missing spaces inside [ ], an unclosed quote, or a missing then / fi / done.

Check:

bash -n script.sh

Fix: review the lines around the reported line number. if needs a matching then and fi; for and while need do and done.

Symptom: a variable comes out empty

Cause: characters follow the variable name directly, so the shell reads it as a different variable.

Check:

echo "DEBUG: var = [$var]"

The brackets let you tell an empty string apart from one containing spaces.

Fix: write ${var} to mark where the variable name ends.

Completion Checklist

  • [ ] Wrote a shebang (#!/bin/bash) on the first line
  • [ ] Added execute permission with chmod +x
  • [ ] Left no spaces around = in assignments
  • [ ] Quoted variable references as "$var"
  • [ ] Ran inside a practice directory, without sudo, to confirm behaviour
  • [ ] Used bash -n and set -x to isolate the cause when it did not work

Next Reading

Share this article

Next steps