Skip to content

Syntax

This chapter covers the fundamental syntax elements of bash scripting. You’ll learn how to create, execute, and structure bash scripts properly. We’ll build a foundation that will support more advanced topics throughout this guide.


Every bash script follows a specific structure. Understanding this structure is essential for writing professional scripts.

┌─────────────────────────────────────────────────────────────────────┐
│ BASH SCRIPT STRUCTURE │
└─────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐
│ Line 1: Shebang │
│ #!/usr/bin/env bash │
│ │ │
│ └── Tells the system which interpreter to use │
│ - #! is called "shebang" │
│ - Can also use: #!/bin/bash │
│ - #!/usr/bin/env bash is more portable │
└───────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐
│ Lines 2+: Comments (optional but recommended) │
│ # │
│ # Script Name: myscript.sh │
│ # Description: This script does something useful │
│ # Author: Your Name │
│ # Date: 2024-01-15 │
│ # │
│ Comments help: │
│ - Document the script's purpose │
│ - Explain complex logic │
│ - Make code maintainable │
└───────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐
│ Lines 3+: Script Body │
│ │
│ set -euo pipefail # Error handling (explained later) │
│ │
│ VARIABLES # Variable declarations │
│ │
│ FUNCTIONS # Function definitions │
│ │
│ MAIN LOGIC # The actual script execution │
└───────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐
│ Optional: Exit Status │
│ exit 0 # 0 = success, non-zero = error │
└───────────────────────────────────────────────────────────────────────┘

The shebang (#!) is the first line that tells the operating system which interpreter to use to execute the script.

# Common shebang variations
# 1. Using absolute path to bash (most common)
#!/bin/bash
# 2. Using env to find bash (more portable)
#!/usr/bin/env bash
# 3. Using absolute path to env
#!/usr/env/bin/bash
# 4. For specific bash version (if needed)
#!/bin/bash -l
# 5. With login shell options
#!/usr/bin/env bash -l

Why #!/usr/bin/env bash is preferred:

  • Automatically finds bash in the PATH
  • Works across different Linux distributions
  • Works on macOS, BSD, and other Unix-like systems
  • More flexible than hardcoded paths

Terminal window
# This is a single line comment
echo "Hello, World!" # This is an inline comment
# ============================================================================
# MULTI-LINE COMMENT BLOCK (using series of #)
# You can create comment blocks like this for section headers
# This is useful for documentation within scripts
# ============================================================================
Terminal window
# Method 1: Use a colon with a here document
: <<'COMMENT'
This is a multi-line comment block.
It can span multiple lines.
Variables like $HOME won't be expanded here.
Useful for disabling code temporarily.
COMMENT
# Method 2: With expansion
: <<COMMENT
This is another multi-line comment.
Variables like $HOME will be expanded: $HOME
Useful for generating documentation.
COMMENT

Let’s create your first bash script step by step.

# Step 1: Create the script file
cat > /tmp/hello_world.sh << 'EOF'
#!/usr/bin/env bash
# hello_world.sh - Your first bash script
# This script prints "Hello, World!" to the console
echo "Hello, World!"
EOF
# Step 2: Make it executable
chmod +x /tmp/hello_world.sh
# Step 3: Run it
/tmp/hello_world.sh

Let’s create a script that’s actually useful for DevOps work.

#!/usr/bin/env bash
#===============================================================================
# system_info.sh - Display system information
#===============================================================================
# Script description:
# This script displays basic system information that DevOps engineers
# commonly need to check when working with servers.
echo "=============================================="
echo " SYSTEM INFORMATION REPORT"
echo "=============================================="
echo ""
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo "OS: $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)"
echo "Uptime: $(uptime -p)"
echo "Load Average: $(cat /proc/loadavg | awk '{print $1, $2, $3}')"
echo ""
echo "CPU Info:"
echo " Model: $(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2)"
echo " Cores: $(nproc)"
echo ""
echo "Memory Info:"
free -h | awk '/^Mem:/ {print " Used: " $3 " / " $2}'
echo ""
echo "Disk Usage:"
df -h | grep -E '^/dev/'
echo ""
echo "Network Interfaces:"
ip -br addr show | grep UP
echo ""
echo "==============================================="
echo " Report Complete"
echo "==============================================="
Terminal window
# Make it executable
chmod +x system_info.sh
# Run it
./system_info.sh
# Or run it directly with bash (doesn't need executable permission)
bash system_info.sh
# Run with absolute path
/home/user/scripts/system_info.sh

Variables in bash are declared by simply assigning a value.

Terminal window
# Basic variable assignment (NO spaces around =)
name="John"
age=30
is_admin=true
# Using variables
echo "Name: $name"
echo "Age: $age"
# Alternative syntax (useful in strings)
echo "Name: ${name}"
Terminal window
# String
name="Alice"
echo "String: $name"
# Integer
count=42
echo "Integer: $count"
# Array (covered in detail later)
colors=("red" "green" "blue")
echo "Array: ${colors[@]}"
# Associative array (bash 4+)
declare -A user_info
user_info["name"]="Bob"
user_info["email"]="bob@example.com"
echo "Associative: ${user_info[name]}"

Terminal window
# Basic usage
echo "Hello, World!"
# Without newline
echo -n "No newline: "
# Enable interpretation of escape sequences
echo -e "Line1\nLine2"
# Common escape sequences:
# \n - New line
# \t - Tab
# \r - Carriage return
# \b - Backspace
# \\ - Backslash
# Examples
echo -e "First line\nSecond line"
echo -e "Column1\tColumn2"
echo -e "This\b removes character"
echo -e "Path: /home/user\\name"
Terminal window
# Basic usage
printf "Hello, World!\n"
# Format specifiers
printf "String: %s\n" "hello"
printf "Integer: %d\n" 42
printf "Float: %.2f\n" 3.14159
# Multiple arguments
printf "%s is %d years old\n" "Alice" 30
# Padding
printf "%10s\n" "hello" # Right align
printf "%-10s\n" "hello" # Left align
printf "%05d\n" 42 # Zero-pad
# Field width
printf "|%10s|%10s|\n" "Name" "Value"
printf "|%-10s|%-10s|\n" "Name" "Value"

┌─────────────────────────────────────────────────────────────────────┐
│ FILE DESCRIPTORS IN BASH │
└─────────────────────────────────────────────────────────────────────┘
┌───────────┬────────────┬─────────────────────────────────────────────┐
│ Descriptor│ Name │ Description │
├───────────┼────────────┼─────────────────────────────────────────────┤
│ 0 │ stdin │ Standard input (keyboard) │
│ 1 │ stdout │ Standard output (terminal) │
│ 2 │ stderr │ Standard error (terminal) │
└───────────┴────────────┴─────────────────────────────────────────────┘
┌───────────────┐
│ Terminal │
└───────┬───────┘
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
stdin (0) stdout (1) stderr (2)
│ │ │
▼ ▼ ▼
┌───────┐ ┌───────────┐ ┌───────────┐
│ Input │ │ Output │ │ Error │
│ (read)│ │ (success) │ │ (issues) │
└───────┘ └───────────┘ └───────────┘
Terminal window
# Redirect stdout to file (overwrite)
echo "Hello" > output.txt
# Redirect stdout to file (append)
echo "Hello" >> output.txt
# Redirect stderr to file
command 2> error.log
# Redirect both stdout and stderr to file
command &> all_output.log
command > output.log 2>&1 # Older syntax
# Redirect stderr to stdout
command 2>&1 | grep "error"
# Discard output (send to /dev/null)
command > /dev/null 2>&1
# Read from file
while read line; do
echo "Line: $line"
done < input.txt
Terminal window
# Example 1: Save output to file
date > /tmp/current_time.txt
echo "Time saved to file"
# Example 2: Append to log file
echo "$(date): Script started" >> /var/log/myscript.log
# Example 3: Redirect errors only
find / -name "file.txt" 2>/dev/null
# Example 4: Custom error logging
command_that_fails 2>&1 | tee -a error.log
# Example 5: Read lines from file
while IFS= read -r line; do
echo "Processing: $line"
done < "config.txt"

Terminal window
# Simple command
ls -la
# Multiple commands on one line
echo "Date:" && date
echo "Uptime:" && uptime
# Run commands sequentially
echo "Step 1"; echo "Step 2"; echo "Step 3"
# Conditional execution
# Run command2 only if command1 succeeds
command1 && command2
# Run command2 only if command1 fails
command1 || command2
Terminal window
# Capture command output into a variable
current_date=$(date)
echo "Today is: $current_date"
# Older syntax (backticks)
current_date=`date`
echo "Today is: $current_date"
# Using in place
echo "Hostname: $(hostname)"
# Nested command substitution
echo "Home dir owner: $(ls -ld $HOME | awk '{print $3}')"

Terminal window
# Every command returns an exit status
# 0 = success
# 1-255 = error (specific meaning varies by command)
# Check exit status of last command
ls -la /tmp
echo "Exit status: $?" # $? contains the exit status
# Test exit status
if [ $? -eq 0 ]; then
echo "Command succeeded"
else
echo "Command failed"
fi
# Using exit in scripts
exit 0 # Success
exit 1 # General error
exit 2 # Misuse of shell command
exit 126 # Command not executable
exit 127 # Command not found
exit 128 # Invalid exit argument
# Example: Exit with status
grep "pattern" file.txt
if [ $? -eq 0 ]; then
echo "Pattern found"
else
echo "Pattern not found"
exit 1
fi

Terminal window
# Double quotes - allow variable expansion
name="World"
echo "Hello, $name" # Output: Hello, World
echo "Date: $(date)" # Output: Date: Sat Jan 15 10:30:00...
# Single quotes - literal (no expansion)
echo 'Hello, $name' # Output: Hello, $name
echo 'Date: $(date)' # Output: Date: $(date)
# Backticks - command substitution (legacy)
echo "Result: `date +%H:%M`"
# Here-string
read -r word <<< "hello world"
echo $word # Output: hello
Terminal window
# Escape special characters
echo "Path: /home/user\tname" # \t = tab
echo "New\nline" # \n = newline
echo "Price: \$10" # \$ = literal $
echo "Backslash: \\" # \\ = literal \
# Using printf for more control
printf '%s\n' "Line 1" "Line 2"
printf '%-10s|%-10s\n' "Left" "Right"

Terminal window
# Integer arithmetic with let
let a=5+3
let b=a*2
let "c = (a + b) / 2"
# Shorthand operators
let a+=5 # Same as: let a=a+5
let b-=3 # Same as: let b=b-3
let c*=2 # Same as: let c=c*2
Terminal window
# Arithmetic expansion
result=$((5 + 3))
result=$((10 - 2))
result=$((4 * 3))
result=$((15 / 4)) # Integer division: 3
result=$((15 % 4)) # Modulo: 3
# Using variables
a=10
b=3
sum=$((a + b))
product=$((a * b))
Terminal window
# expr command (older method)
result=$(expr 5 + 3)
result=$(expr 10 - 2)
# Note: spaces are required
result=$(expr 5+3) # Wrong! Returns "5+3"
result=$(expr 5 + 3) # Correct! Returns "8"
Terminal window
# bc for floating point
result=$(echo "10 / 3" | bc -l)
echo $result # 3.33333...
# Scale (precision)
result=$(echo "scale=2; 10 / 3" | bc)
echo $result # 3.33
# More complex math
result=$(echo "sqrt(16) + 2^3" | bc -l)
echo $result # 12.00000

Terminal window
# String concatenation
str1="Hello"
str2="World"
combined="$str1 $str2"
echo $combined # Hello World
# String length
text="Hello World"
length=${#text}
echo $length # 11
# Substring
text="Hello World"
echo ${text:0:5} # Hello
echo ${text:6:5} # World
# String replacement
text="Hello World"
echo ${text/World/Universe} # Hello Universe
echo ${text//l/L} # HeLLo WorLd (replace all)
# Pattern-based operations
filename="report-2024-01-15.csv"
echo ${filename%.csv} # report-2024-01-15 (remove extension)
echo ${filename##*-} # 01-15.csv (remove prefix)

Terminal window
# Declare array
fruits=("apple" "banana" "cherry")
# Access elements
echo ${fruits[0]} # apple
echo ${fruits[1]} # banana
echo ${fruits[2]} # cherry
# All elements
echo ${fruits[@]} # apple banana cherry
echo ${fruits[*]} # apple banana cherry
# Array length
echo ${#fruits[@]} # 3
# Add element
fruits+="orange"
fruits+=(grape)
# Specific element length
echo ${#fruits[0]} # 5 (length of "apple")

Terminal window
# Numeric comparisons
[ 5 -eq 5 ] # Equal
[ 5 -ne 3 ] # Not equal
[ 5 -gt 3 ] # Greater than
[ 5 -lt 10 ] # Less than
[ 5 -ge 5 ] # Greater or equal
[ 3 -le 5 ] # Less or equal
# String comparisons
[ "abc" = "abc" ] # Equal
[ "abc" != "xyz" ] # Not equal
[ -z "" ] # Empty string
[ -n "text" ] # Non-empty string
# File tests
[ -f "file.txt" ] # Regular file exists
[ -d "dir" ] # Directory exists
[ -r "file" ] # File is readable
[ -w "file" ] # File is writable
[ -x "file" ] # File is executable
[ -e "file" ] # File exists
[ -s "file" ] # File exists and is non-empty
# Combining tests
[ -f "file" ] && [ -r "file" ]
[ -d "dir" ] || [ -d "backup" ]
Terminal window
# [[ is more powerful than [
# Supports pattern matching and regex
# Pattern matching
[[ "filename.txt" == *.txt ]] # True
[[ "hello" == h* ]] # True
# Regex (bash 3+)
[[ "email@example.com" =~ ^[a-z]+@[a-z]+\.[a-z]+$ ]]
# No word splitting (safer)
text="hello world"
[[ $text == "hello world" ]] # Works correctly
# Combined with && and ||
[[ -f "file" ]] && echo "File exists"

Terminal window
# Basic if
if [ condition ]; then
echo "Condition is true"
fi
# If-else
if [ condition ]; then
echo "True"
else
echo "False"
fi
# If-elif-else
if [ $status = "running" ]; then
echo "Service is running"
elif [ $status = "stopped" ]; then
echo "Service is stopped"
else
echo "Unknown status"
fi

#!/usr/bin/env bash
# check_file.sh - Check if a file exists and is readable
FILE="${1:-/etc/passwd}"
if [ -f "$FILE" ]; then
echo "File exists: $FILE"
if [ -r "$FILE" ]; then
echo "File is readable"
echo "First 5 lines:"
head -5 "$FILE"
else
echo "File is NOT readable"
exit 1
fi
else
echo "File does not exist: $FILE"
exit 1
fi
#!/usr/bin/env bash
# calculator.sh - Simple arithmetic calculator
if [ $# -ne 3 ]; then
echo "Usage: $0 <num1> <operator> <num2>"
echo "Operators: + - * / %"
exit 1
fi
num1=$1
operator=$2
num2=$3
case $operator in
+) result=$((num1 + num2)) ;;
-) result=$((num1 - num2)) ;;
\*) result=$((num1 * num2)) ;;
/) result=$((num1 / num2)) ;;
%) result=$((num1 % num2)) ;;
*) echo "Invalid operator: $operator"
exit 1 ;;
esac
echo "$num1 $operator $num2 = $result"
#!/usr/bin/env bash
# backup.sh - Simple backup script
set -euo pipefail
SOURCE_DIR="/home/user/documents"
BACKUP_DIR="/tmp/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_${TIMESTAMP}.tar.gz"
echo "Starting backup..."
echo "Source: $SOURCE_DIR"
echo "Destination: $BACKUP_DIR/$BACKUP_NAME"
# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"
# Create the backup
tar -czf "$BACKUP_DIR/$BACKUP_NAME" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
# Check if backup was successful
if [ -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
size=$(du -h "$BACKUP_DIR/$BACKUP_NAME" | cut -f1)
echo "Backup successful! Size: $size"
echo "Location: $BACKUP_DIR/$BACKUP_NAME"
else
echo "Backup failed!"
exit 1
fi

Terminal window
# Issue 1: Spaces around = in assignment
# Wrong:
name = "value" # This tries to run "name" as a command
# Correct:
name="value"
# Issue 2: Unquoted variables
# Wrong:
filename="my file.txt"
rm $filename # May fail if filename has spaces
# Correct:
rm "$filename"
# Issue 3: Missing command arguments
# Wrong:
grep pattern # Missing file argument
# Correct:
grep pattern file.txt
# Issue 4: Not checking exit status
# Wrong:
grep "pattern" file.txt
echo "Found it" # Runs even if grep fails
# Correct:
grep "pattern" file.txt && echo "Found it"
Terminal window
# Run script in debug mode
bash -x script.sh
# Enable debug within script
set -x # Start debugging
echo "This will be traced"
set +x # Stop debugging
# Trace specific sections
#!/bin/bash
echo "Normal execution"
(
set -x
echo "Debug this section"
ls -la
set +x
)
echo "Back to normal"

In this chapter, you learned:

  • ✅ How to structure a bash script properly
  • ✅ The importance of the shebang line
  • ✅ How to use comments effectively
  • ✅ Creating and running your first scripts
  • ✅ Working with variables
  • ✅ Basic commands: echo and printf
  • ✅ Input/output redirection
  • ✅ Command execution methods
  • ✅ Exit status and why it matters
  • ✅ Working with quotes and escaping
  • ✅ Basic arithmetic operations
  • ✅ String operations
  • ✅ Introduction to arrays
  • ✅ Conditional testing basics
  • ✅ Debugging techniques

  1. Create a script that prints your name and today’s date
  2. Create a script that calculates the sum of two numbers
  3. Write a script that checks if a file exists
  1. Create a script that backs up a directory to /tmp
  2. Write a script that takes a filename as argument and checks if it’s readable
  3. Create a simple calculator that handles division
  1. Write a script that processes command-line arguments properly
  2. Create a script with proper error handling and logging
  3. Implement a simple menu system using case statements

Now that you understand basic syntax, continue to the next chapter to learn about variables and parameters in detail.


Previous Chapter: Environment Setup Next Chapter: Variables and Parameters