DEV Community

Cover image for The Ultimate Bash Scripting Tutorial: From Beginner to Advanced
Mohammad Aman
Mohammad Aman

Posted on

The Ultimate Bash Scripting Tutorial: From Beginner to Advanced

Bash scripting is a powerful tool for automating tasks, managing systems, and boosting your productivity as a developer. Whether you're a command-line newbie or a seasoned coder, this guide will take you from Bash basics to advanced scripting with practical examples, tips, and resources.

What is Bash?

Bash (Bourne Again SHell) is a Unix shell and command language. It's the default shell on:

  • Linux distributions

  • macOS (until Catalina, now Zsh)

  • Windows Subsystem for Linux (WSL)

  • Most Unix-like systems

Why Learn Bash Scripting?

  • 🚀 Automation: Save hours by scripting repetitive tasks

  • 🛠 System Administration: Essential for DevOps and sysadmins

  • 💻 Developer Productivity: Combine tools and process data quickly

  • 📊 Data Processing: Powerful text manipulation capabilities

Getting Started

Your First Bash Script

  1. Create a new file:

    touch hello.sh 
  2. Add the following content:

    #!/bin/bash # This is a comment echo "Hello, World!" 
  3. Make it executable:

    chmod +x hello.sh 
  4. Run it:

    ./hello.sh 

Key Components:

  • #!/bin/bash (Shebang line) tells the system to use Bash

  • # indicates comments

  • echo prints text to the terminal

Pro Tip: Always include the shebang line for portability, even though Bash is often the default.

Variables and Parameters

Basic Variables

name="Mohammad" # No spaces around = age=10 greeting="Good morning, $name!" 
Enter fullscreen mode Exit fullscreen mode

Accessing Variables:

echo "$name is $age years old." # Mohammad is 10 years old. echo "Message: $greeting" # Good morning, Mohammad! 
Enter fullscreen mode Exit fullscreen mode

Command Substitution

Store command output in a variable:

current_date=$(date) echo "Today is $current_date" # Alternative syntax files=`ls` echo "Files: $files" 
Enter fullscreen mode Exit fullscreen mode

Positional Parameters

Access command-line arguments:

#!/bin/bash echo "Script name: $0" echo "First argument: $1" echo "Second argument: $2" echo "All arguments: $@" echo "Number of arguments: $#" 
Enter fullscreen mode Exit fullscreen mode

Run with:

./script.sh arg1 arg2 arg3 
Enter fullscreen mode Exit fullscreen mode

Special Variables

Variable Description
$0 Script name
$1-$9 Positional arguments
$# Number of arguments
$@ All arguments as separate strings
$* All arguments as single string
$? Exit status of last command
$$ Process ID (PID) of script
$! PID of last background command

Using getopts for Flags

Handle command-line options professionally:

#!/bin/bash while getopts "f:d:v" opt; do case $opt in f) file="$OPTARG" ;; d) dir="$OPTARG" ;; v) verbose=true ;; \?) echo "Invalid option: -$OPTARG" >&2 exit 1 ;; esac done echo "File: ${file:-not specified}" echo "Directory: ${dir:-not specified}" echo "Verbose: ${verbose:-false}" 
Enter fullscreen mode Exit fullscreen mode

Usage:

./script.sh -f data.txt -d /home/user -v 
Enter fullscreen mode Exit fullscreen mode

Best Practices:

  • Always quote variables: "$var"

  • Use ${var:-default} for default values

  • Prefer $@ over $* to preserve argument separation

Control Structures

Conditionals (if/elif/else)

Basic Syntax:

if [[ condition ]]; then # commands elif [[ another_condition ]]; then # commands else # commands fi 
Enter fullscreen mode Exit fullscreen mode

Common Test Operators:

Operator Description Example
-eq Equal [[ "$a" -eq "$b" ]]
-ne Not equal [[ "$a" -ne "$b" ]]
-lt Less than [[ "$a" -lt "$b" ]]
-gt Greater than [[ "$a" -gt "$b" ]]
-z String is empty [[ -z "$str" ]]
-n String is not empty [[ -n "$str" ]]
== String equality [[ "$str1" == "$str2" ]]
=~ Regex match [[ "$str" =~ ^[0-9]+$ ]]
-e File exists [[ -e "file.txt" ]]
-d Directory exists [[ -d "/path" ]]

Example:

#!/bin/bash read -p "Enter temperature (°C): " temp if [[ "$temp" -gt 30 ]]; then echo "It's hot outside! 🥵" elif [[ "$temp" -lt 10 ]]; then echo "It's cold outside! ❄️" else echo "It's pleasant weather. 🌞" fi 
Enter fullscreen mode Exit fullscreen mode

Case Statements

#!/bin/bash read -p "Enter your favorite fruit: " fruit case "$fruit" in apple) echo "Apples are crunchy!" ;; banana) echo "Bananas are rich in potassium!" ;; orange|lemon) echo "Citrus fruits are vitamin C rich!" ;; *) echo "I don't know about $fruit" ;; esac 
Enter fullscreen mode Exit fullscreen mode

Loops

For Loop:

# Basic for loop for i in {1..5}; do echo "Number: $i" done # C-style for loop for ((i=0; i<5; i++)); do echo "Count: $i" done # Iterate over files for file in *.txt; do echo "Processing $file" done 
Enter fullscreen mode Exit fullscreen mode

While Loop:

count=1 while [[ $count -le 5 ]]; do echo "Count: $count" ((count++)) done # Reading file line by line while IFS= read -r line; do echo "Line: $line" done < "file.txt" 
Enter fullscreen mode Exit fullscreen mode

Until Loop:

attempt=1 until ping -c1 example.com &>/dev/null; do echo "Attempt $attempt: Waiting for connection..." ((attempt++)) sleep 1 done echo "Connection established!" 
Enter fullscreen mode Exit fullscreen mode

Loop Control:

  • break: Exit the loop

  • continue: Skip to next iteration

for i in {1..10}; do if [[ $i -eq 5 ]]; then continue fi if [[ $i -eq 8 ]]; then break fi echo "Number: $i" done 
Enter fullscreen mode Exit fullscreen mode

Functions

Basic Function

greet() { local name="$1" # Local variable echo "Hello, $name!" } greet "Alice" # Output: Hello, Alice! greet "Bob" # Output: Hello, Bob! 
Enter fullscreen mode Exit fullscreen mode

Returning Values

add() { local sum=$(( $1 + $2 )) echo "$sum" # Output the result } result=$(add 3 5) echo "3 + 5 = $result" # 3 + 5 = 8 
Enter fullscreen mode Exit fullscreen mode

Function Parameters

create_user() { local username="$1" local shell="${2:-/bin/bash}" # Default value echo "Creating user $username with shell $shell" # useradd "$username" -s "$shell" } create_user "Mohammad" create_user "Aman" "/bin/zsh" 
Enter fullscreen mode Exit fullscreen mode

Best Practices:

  • Use local for function variables to avoid side effects

  • Return status codes (0 for success) with return

  • Use echo to "return" data

  • Document functions with comments

Error Handling

Exit Codes

Every command returns an exit code (0 = success, non-zero = error):

mkdir /non_existent_dir if [[ $? -ne 0 ]]; then echo "Failed to create directory!" >&2 exit 1 fi 
Enter fullscreen mode Exit fullscreen mode

The set Command

Control script behavior:

set -e # Exit immediately if a command fails set -u # Treat unset variables as an error set -o pipefail # Fail if any command in a pipeline fails set -x # Print commands before execution (debugging) 
Enter fullscreen mode Exit fullscreen mode

Trapping Signals

cleanup() { echo "Cleaning up..." rm -f tempfile.txt } trap cleanup EXIT INT TERM # Run on exit or interrupt 
Enter fullscreen mode Exit fullscreen mode

Logging

log() { local message="$1" local level="${2:-INFO}" local timestamp=$(date +"%Y-%m-%d %H:%M:%S") echo "[$timestamp] [$level] $message" >> script.log } log "Starting script execution" log "Error encountered" "ERROR" 
Enter fullscreen mode Exit fullscreen mode

Advanced Topics

Arrays

# Indexed arrays fruits=("apple" "banana" "cherry") echo ${fruits[0]} # apple echo ${fruits[@]} # all elements echo ${#fruits[@]} # array length # Adding elements fruits+=("date") # Associative arrays (Bash 4+) declare -A user user["name"]="Sakir" user["age"]=20 echo "${user["name"]} is ${user["age"]} years old." 
Enter fullscreen mode Exit fullscreen mode

String Manipulation

str="Hello World" # Substrings echo ${str:0:5} # Hello echo ${str:6} # World # Replacement echo ${str/World/Everyone} # Hello Everyone echo ${str//l/L} # HeLLo WorLd (global replacement) # Case conversion echo ${str^^} # HELLO WORLD echo ${str,,} # hello world 
Enter fullscreen mode Exit fullscreen mode

Arithmetic Operations

a=5 b=3 # Using $(( )) echo $((a + b)) # 8 echo $((a * b)) # 15 echo $((a++)) # 5 (then a becomes 6) # Using let let "sum = a + b" echo $sum # 9 (if a is now 6) # Using expr (older syntax) echo $(expr $a + $b) 
Enter fullscreen mode Exit fullscreen mode

Process Substitution

# Compare outputs diff <(ls dir1) <(ls dir2) # Process multiple files paste <(cut -f1 file1) <(cut -f2 file2) > output.txt 
Enter fullscreen mode Exit fullscreen mode

Here Documents

cat <<EOF This is a multi-line text block that preserves formatting and variables like $HOME EOF # With variable expansion disabled cat <<'EOF' No variable expansion here $HOME EOF 
Enter fullscreen mode Exit fullscreen mode

Performance Tips

  • Avoid unnecessary subshells:

    # Slow count=$(ls | wc -l) # Faster files=(*) count=${#files[@]} 
  • Use built-in string operations instead of external commands:

    # Instead of: uppercase=$(echo "$str" | tr '[:lower:]' '[:upper:]') # Use: uppercase="${str^^}" 
  • Batch file operations:

    # Instead of: for file in *.txt; do process "$file" done # Consider: find . -name "*.txt" -exec process {} \; 
  • Use printf instead of echo for complex output:

    printf "Name: %-20s Age: %3d\n" "$name" "$age" 

Security Best Practices

  • Always quote variables:

    rm "$file" # Safe rm $file # Dangerous (filename with spaces becomes multiple arguments) 
  • Validate input:

    if [[ ! "$input" =~ ^[a-zA-Z0-9_]+$ ]]; then echo "Invalid input" >&2 exit 1 fi 
  • Use mktemp for temporary files:

    tempfile=$(mktemp /tmp/script.XXXXXX) echo "Data" > "$tempfile" 
  • Avoid eval - it can execute arbitrary code:

    # Dangerous! eval "$user_input" 
  • Run with least privileges:

    # Use sudo only when necessary sudo chown root:root /important/file 

Real-World Script Examples

System Backup Script

#!/bin/bash # backup.sh - Automated system backup set -euo pipefail BACKUP_DIR="/backups/$(date +%Y-%m-%d_%H-%M-%S)" LOG_FILE="/var/log/backup.log" mkdir -p "$BACKUP_DIR" log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } log "Starting backup to $BACKUP_DIR" # Backup home directories for user in /home/*; do if [[ -d "$user" ]]; then username=$(basename "$user") log "Backing up $username" tar -czf "$BACKUP_DIR/$username.tar.gz" "$user" fi done # Backup system configurations log "Backing up system configurations" tar -czf "$BACKUP_DIR/etc.tar.gz" /etc log "Backup completed successfully" 
Enter fullscreen mode Exit fullscreen mode

Log Analyzer

#!/bin/bash # log_analyzer.sh - Analyze log files if [[ $# -eq 0 ]]; then echo "Usage: $0 <logfile> [error_level]" exit 1 fi LOG_FILE="$1" ERROR_LEVEL="${2:-ERROR}" if [[ ! -f "$LOG_FILE" ]]; then echo "Error: File $LOG_FILE not found" >&2 exit 1 fi echo "Analyzing $LOG_FILE for $ERROR_LEVEL messages" declare -A error_counts while IFS= read -r line; do if [[ "$line" =~ \[([^]]+)\] ]]; then timestamp="${BASH_REMATCH[1]}" date_part="${timestamp%% *}" if [[ "$line" =~ $ERROR_LEVEL ]]; then ((error_counts["$date_part"]++)) fi fi done < "$LOG_FILE" echo -e "\nError Count by Date:" for date in "${!error_counts[@]}"; do printf "%s: %d\n" "$date" "${error_counts[$date]}" done | sort 
Enter fullscreen mode Exit fullscreen mode

Cheat Sheet

Variable Manipulation

Syntax Description
${var} Variable value
${var:-default} Use default if var is unset
${var:=default} Set default if var is unset
${#var} Length of string
${var:position:length} Substring
${var/pattern/repl} Replace first match
${var//pattern/repl} Replace all matches

Test Operators

Operator Description
-e File exists
-f Regular file
-d Directory
-r Readable
-w Writable
-x Executable
-z String is empty
-n String is not empty

Arithmetic

Operator Description
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulus
** Exponentiation

Resources

Documentation

Tools

Books

Communities

Conclusion

Bash scripting is a must-have skill for developers and system administrators. Start with simple automation tasks and gradually incorporate advanced features as you gain confidence. Key takeaways:

  • Test thoroughly: Especially for scripts that modify files or systems

  • Add comments: Document your code for future you

  • Follow conventions: Make your scripts readable and maintainable

  • Share your work: Contribute to open source or publish your utilities

Next Steps:

  • Explore awk and sed for advanced text processing

  • Learn about cron for scheduling scripts

  • Dive into shell scripting frameworks like Bash Infinity

Happy scripting! 🐚💻

Discussion Prompt: What's your favorite Bash scripting trick? Share in the comments below! 👇

Top comments (1)

Collapse
 
hng_trtrn_67bc14029b profile image
Hương Trà Trần

Image description

Hi, I tried this, but it does not work. Do you have any suggestions?