Bash Scripting for Beginners: Your First Scripts – Complete Guide
Knowledge Overview
Prerequisites
basic terminale knowledge
Time Investment
20 minutes reading time
40-60 minutes hands-on practice
Guide Content
What is Bash Scripting?
Bash scripting for beginners is the process of writing executable text files containing sequential shell commands to automate repetitive tasks in Linux systems. A bash script starts with #!/bin/bash (shebang), contains commands you'd normally type in the terminal, and can be executed like any program. For example, create a file with .sh extension, write your commands, make it executable with chmod +x script.sh, and run it with ./script.sh.
Quick Start Example:
#!/bin/bash
# My First Bash Script
echo "Hello, World!"
echo "Today is $(date)"
Table of Contents
- What is Bash Scripting and Why Learn It?
- How to Create Your First Bash Script
- Understanding Bash Script Variables
- How to Use Conditional Statements in Bash
- Bash Script Loops Tutorial
- Working with Functions in Bash Scripts
- Best Practices for Shell Scripting
- Troubleshooting Common Bash Script Errors
- FAQ: Bash Scripting for Beginners
What is Bash Scripting and Why Learn It?
Bash (Bourne Again Shell) scripting is a fundamental skill for Linux system administrators, developers, and DevOps engineers. Moreover, bash scripting for beginners provides an accessible entry point into automation and system administration tasks.
Why Bash Scripts Matter
Bash scripts enable you to:
- Automate repetitive tasks like backups, log rotation, and system updates
- Chain multiple commands together with error handling
- Schedule tasks using cron jobs for automated execution
- Standardize procedures across multiple servers
- Save time by eliminating manual command execution
Furthermore, bash scripting serves as a gateway to understanding Linux system internals, process management, and file operations. Therefore, mastering bash scripts enhances your overall Linux proficiency.
Real-World Use Cases:
- System health monitoring and alerting
- Automated deployment pipelines
- Database backup automation
- User account provisioning
- Log file analysis and reporting
- Batch file processing
How to Create Your First Bash Script
Creating your first bash script involves several straightforward steps. Additionally, understanding the basic structure ensures your scripts run correctly across different Linux distributions.
1: Choose Your Text Editor
You can use any text editor to write bash scripts:
# Using nano (beginner-friendly)
nano myscript.sh
# Using vim (advanced)
vim myscript.sh
# Using VS Code
code myscript.sh
2: Write the Shebang Line
The shebang (#!) tells the system which interpreter to use. Consequently, this line must be the first line in your script:
#!/bin/bash
Alternative shebangs:
#!/usr/bin/env bash # More portable across systems
#!/bin/sh # POSIX-compliant shell
3: Add Your Commands
Here's a complete beginner script example:
#!/bin/bash
# Script: hello_world.sh
# Description: My first bash script
# Author: Your Name
# Date: 2025-01-07
echo "================================"
echo " Welcome to Bash Scripting!"
echo "================================"
echo ""
echo "Current user: $USER"
echo "Home directory: $HOME"
echo "Current directory: $(pwd)"
echo "Today's date: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
echo "System information:"
uname -a
4: Make the Script Executable
Scripts need execute permissions to run. Therefore, use the chmod command:
chmod +x hello_world.sh
Understanding permissions:
# View current permissions
ls -l hello_world.sh
# Output: -rwxr-xr-x 1 user group 245 Jan 07 10:30 hello_world.sh
# βββββββββ
# |||||||||
# Owner: rwx (read, write, execute)
# Group: r-x (read, execute)
# Others: r-x (read, execute)
5: Execute Your Script
Run your script using one of these methods:
# Method 1: Direct execution (requires execute permission)
./hello_world.sh
# Method 2: Explicit bash interpreter
bash hello_world.sh
# Method 3: Using sh (POSIX compatible)
sh hello_world.sh
# Method 4: Source the script (runs in current shell)
source hello_world.sh
# or
. hello_world.sh
Expected Output:
================================
Welcome to Bash Scripting!
================================
Current user: john
Home directory: /home/john
Current directory: /home/john/scripts
Today's date: 2025-01-07 10:35:42
System information:
Linux hostname 5.15.0-91-generic #101-Ubuntu SMP x86_64 GNU/Linux
Understanding Bash Script Variables
Variables are fundamental to bash scripting for beginners. Moreover, they store data that can be referenced and manipulated throughout your script.
How to Declare Variables in Bash
Variables in bash are declared without type keywords. Additionally, there should be no spaces around the equals sign:
#!/bin/bash
# String variables
name="John Doe"
email="john@example.com"
# Integer variables
age=30
count=100
# Command substitution
current_date=$(date)
user_home=$HOME
# Array variables
fruits=("apple" "banana" "cherry")
echo "Name: $name"
echo "Email: $email"
echo "Age: $age"
echo "Date: $current_date"
echo "First fruit: ${fruits[0]}"
Variable Naming Conventions
Follow these best practices when naming variables:
| Rule | Example | Why |
|---|---|---|
| Use descriptive names | backup_directory not bd | Improves readability |
| Use lowercase for local | temp_file | Convention standard |
| Use uppercase for constants | MAX_RETRY=3 | Indicates immutability |
| Use underscores | log_file_path | Separates words clearly |
| Avoid special characters | user_name not user-name | Prevents syntax errors |
Special Variables in Bash
Bash provides several built-in special variables. Furthermore, these variables offer valuable information about the script's execution context:
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
echo "Process ID: $$"
echo "Last command exit status: $?"
echo "Current user: $USER"
echo "Home directory: $HOME"
echo "Current working directory: $PWD"
Testing special variables:
# Save as test_vars.sh
./test_vars.sh arg1 arg2 arg3
# Output:
# Script name: ./test_vars.sh
# First argument: arg1
# Second argument: arg2
# All arguments: arg1 arg2 arg3
# Number of arguments: 3
# Process ID: 12345
Variable Expansion and Quoting
Understanding quoting prevents common errors. Consequently, proper quoting ensures variables expand correctly:
#!/bin/bash
text="Hello World"
# Unquoted (word splitting occurs)
echo $text # Output: Hello World
# Double quotes (preserves whitespace, allows variable expansion)
echo "$text" # Output: Hello World
# Single quotes (treats everything as literal)
echo '$text' # Output: $text
# No quotes with special characters (WRONG)
file=my file.txt # ERROR: unexpected syntax
# Correct way
file="my file.txt" # Correct
file='my file.txt' # Also correct
Advanced variable manipulation:
#!/bin/bash
# String length
name="Linux"
echo "Length: ${#name}" # Output: 5
# Substring
echo "First 3 chars: ${name:0:3}" # Output: Lin
# Default values
echo "Value: ${undefined_var:-default}" # Output: default
# Variable replacement
path="/home/user/documents/file.txt"
echo "Filename: ${path##*/}" # Output: file.txt
echo "Directory: ${path%/*}" # Output: /home/user/documents
How to Use Conditional Statements in Bash
Conditional statements enable bash scripting for beginners to make decisions. Additionally, these constructs allow scripts to respond differently based on varying conditions.
Basic If-Then-Else Structure
The fundamental conditional structure in bash follows this syntax:
#!/bin/bash
# Simple if statement
age=25
if [ $age -ge 18 ]; then
echo "You are an adult"
fi
# If-else statement
if [ $age -lt 18 ]; then
echo "You are a minor"
else
echo "You are an adult"
fi
# If-elif-else statement
if [ $age -lt 13 ]; then
echo "You are a child"
elif [ $age -lt 18 ]; then
echo "You are a teenager"
else
echo "You are an adult"
fi
Comparison Operators Reference
Understanding comparison operators is essential. Therefore, here's a comprehensive reference table:
Numeric Comparisons:
| Operator | Description | Example |
|---|---|---|
-eq | Equal to | [ $a -eq $b ] |
-ne | Not equal to | [ $a -ne $b ] |
-gt | Greater than | [ $a -gt $b ] |
-ge | Greater than or equal | [ $a -ge $b ] |
-lt | Less than | [ $a -lt $b ] |
-le | Less than or equal | [ $a -le $b ] |
String Comparisons:
| Operator | Description | Example |
|---|---|---|
= or == | Equal to | [ "$a" = "$b" ] |
!= | Not equal to | [ "$a" != "$b" ] |
< | Less than (lexicographic) | [[ "$a" < "$b" ]] |
> | Greater than (lexicographic) | [[ "$a" > "$b" ]] |
-z | String is empty | [ -z "$string" ] |
-n | String is not empty | [ -n "$string" ] |
File Test Operators:
| Operator | Description | Example |
|---|---|---|
-e | File exists | [ -e /path/file ] |
-f | Regular file exists | [ -f /path/file ] |
-d | Directory exists | [ -d /path/dir ] |
-r | File is readable | [ -r /path/file ] |
-w | File is writable | [ -w /path/file ] |
-x | File is executable | [ -x /path/file ] |
-s | File is not empty | [ -s /path/file ] |
Practical Conditional Examples
Here's a real-world script demonstrating conditionals:
#!/bin/bash
# Script: check_system.sh
# Description: System health checker
LOG_FILE="/var/log/syslog"
DISK_THRESHOLD=80
echo "=== System Health Check ==="
echo ""
# Check if log file exists
if [ -f "$LOG_FILE" ]; then
echo "β Log file exists: $LOG_FILE"
# Check if log file is readable
if [ -r "$LOG_FILE" ]; then
echo "β Log file is readable"
else
echo "β Log file is not readable"
exit 1
fi
else
echo "β Log file not found: $LOG_FILE"
exit 1
fi
# Check disk usage
disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ $disk_usage -gt $DISK_THRESHOLD ]; then
echo "β WARNING: Disk usage is ${disk_usage}% (threshold: ${DISK_THRESHOLD}%)"
exit 1
else
echo "β Disk usage is healthy: ${disk_usage}%"
fi
# Check if running as root
if [ "$EUID" -eq 0 ]; then
echo "β Running with root privileges"
else
echo "β Running without root privileges"
fi
echo ""
echo "System check completed successfully!"
Using Double Brackets [[ ]]
Modern bash supports double brackets, which offer enhanced features. Moreover, double brackets provide better string handling:
#!/bin/bash
string="Hello World"
# Pattern matching (only works with [[]])
if [[ $string == Hello* ]]; then
echo "String starts with 'Hello'"
fi
# Regular expression matching
if [[ $string =~ ^[A-Z] ]]; then
echo "String starts with uppercase letter"
fi
# Logical operators with double brackets
age=25
name="John"
if [[ $age -gt 18 && $name == "John" ]]; then
echo "Adult named John"
fi
# No word splitting issues
if [[ -f $HOME/.bashrc ]]; then
echo "Bashrc exists"
fi
Bash Script Loops Tutorial
Loops enable automation of repetitive tasks. Furthermore, bash scripting for beginners becomes powerful when combined with loop constructs.
For Loop: Iterate Over Lists
The for loop iterates over a list of items. Additionally, it's perfect for processing files, arrays, or sequences:
#!/bin/bash
# Loop through a list of values
echo "Fruits:"
for fruit in apple banana cherry grape; do
echo " - $fruit"
done
# Loop through files in directory
echo ""
echo "Files in current directory:"
for file in *.txt; do
if [ -f "$file" ]; then
echo " Processing: $file"
fi
done
# Loop through a range of numbers
echo ""
echo "Counting 1 to 5:"
for i in {1..5}; do
echo " Count: $i"
done
# C-style for loop
echo ""
echo "Even numbers 0 to 10:"
for ((i=0; i<=10; i+=2)); do
echo " $i"
done
While Loop: Condition-Based Iteration
The while loop continues until a condition becomes false. Therefore, it's ideal for monitoring or waiting:
#!/bin/bash
# Basic while loop
counter=1
while [ $counter -le 5 ]; do
echo "Iteration: $counter"
counter=$((counter + 1))
done
# Reading file line by line
echo ""
echo "Reading /etc/passwd (first 5 lines):"
line_count=0
while IFS=: read -r username password uid gid comment home shell; do
echo "User: $username, UID: $uid, Home: $home"
line_count=$((line_count + 1))
[ $line_count -ge 5 ] && break
done < /etc/passwd
# Infinite loop with break condition
echo ""
echo "Monitoring process (Ctrl+C to stop):"
while true; do
load=$(uptime | awk '{print $(NF-2)}' | sed 's/,//')
echo "Current load: $load"
# Break if load exceeds threshold
if (( $(echo "$load > 2.0" | bc -l) )); then
echo "Load too high! Exiting..."
break
fi
sleep 5
done
Until Loop: Inverse While Loop
The until loop runs until a condition becomes true. Consequently, it's useful for waiting operations:
#!/bin/bash
# Wait for file to appear
target_file="/tmp/ready.flag"
counter=0
echo "Waiting for $target_file to appear..."
until [ -f "$target_file" ] || [ $counter -gt 10 ]; do
echo " Attempt $counter: File not found"
sleep 2
counter=$((counter + 1))
done
if [ -f "$target_file" ]; then
echo "File found!"
else
echo "Timeout: File did not appear"
fi
Loop Control: Break and Continue
Control loop execution flow with break and continue:
#!/bin/bash
# Using continue to skip iterations
echo "Odd numbers from 1 to 10:"
for i in {1..10}; do
# Skip even numbers
if [ $((i % 2)) -eq 0 ]; then
continue
fi
echo " $i"
done
# Using break to exit loop early
echo ""
echo "Finding first .log file:"
for file in /var/log/*; do
if [[ $file == *.log ]]; then
echo "Found: $file"
break
fi
done
Working with Functions in Bash Scripts
Functions modularize code and improve reusability. Moreover, they make bash scripting for beginners more organized and maintainable.
How to Define Functions in Bash
Functions can be defined using two syntax styles:
#!/bin/bash
# Method 1: Using 'function' keyword
function greet() {
echo "Hello, $1!"
}
# Method 2: Without 'function' keyword (more common)
say_goodbye() {
echo "Goodbye, $1!"
}
# Calling functions
greet "World"
say_goodbye "Friend"
Functions with Parameters
Functions accept parameters just like scripts. Furthermore, they can return values using exit codes:
#!/bin/bash
# Function with multiple parameters
create_backup() {
local source_dir=$1
local backup_dir=$2
local timestamp=$(date +%Y%m%d_%H%M%S)
if [ -d "$source_dir" ]; then
echo "Creating backup of $source_dir..."
tar -czf "${backup_dir}/backup_${timestamp}.tar.gz" "$source_dir"
if [ $? -eq 0 ]; then
echo "Backup created successfully"
return 0
else
echo "Backup failed"
return 1
fi
else
echo "Error: Source directory does not exist"
return 2
fi
}
# Function that returns a value via echo
get_disk_usage() {
local path=${1:-.}
du -sh "$path" | awk '{print $1}'
}
# Using the functions
create_backup "/home/user/documents" "/backup"
echo "Exit code: $?"
echo ""
echo "Disk usage of /var: $(get_disk_usage /var)"
Local vs Global Variables
Variable scope affects how variables are accessed. Therefore, use local keyword for function-specific variables:
#!/bin/bash
# Global variable
global_var="I am global"
demo_scope() {
local local_var="I am local"
global_var="Modified global"
echo "Inside function:"
echo " Local: $local_var"
echo " Global: $global_var"
}
echo "Before function:"
echo " Global: $global_var"
demo_scope
echo ""
echo "After function:"
echo " Global: $global_var"
# echo " Local: $local_var" # This would be empty
Practical Function Library Example
Create reusable function libraries:
#!/bin/bash
# File: logger_lib.sh
# Description: Logging utility functions
LOG_FILE="/var/log/myscript.log"
LOG_LEVEL="INFO"
log_message() {
local level=$1
shift
local message="$@"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
log_info() {
log_message "INFO" "$@"
}
log_warning() {
log_message "WARNING" "$@"
}
log_error() {
log_message "ERROR" "$@"
}
check_root() {
if [ "$EUID" -ne 0 ]; then
log_error "This script must be run as root"
exit 1
fi
}
# Usage example
log_info "Script started"
check_root
log_warning "Disk space running low"
log_error "Failed to connect to database"
Best Practices for Shell Scripting
Following best practices ensures your scripts are reliable and maintainable. Additionally, these guidelines prevent common pitfalls in bash scripting for beginners.
Script Header and Documentation
Always include comprehensive headers. Moreover, document your script's purpose and usage:
#!/bin/bash
################################################################################
# Script Name: system_backup.sh
# Description: Automated system backup with rotation
# Author: John Doe
# Email: john@example.com
# Date Created: 2025-01-07
# Date Modified: 2025-01-07
# Version: 1.0.0
#
# Usage: ./system_backup.sh [OPTIONS]
# Options:
# -d, --directory DIR Backup directory (default: /backup)
# -r, --retention DAYS Retention period (default: 7)
# -h, --help Display this help message
#
# Dependencies: tar, gzip, date
# Exit Codes:
# 0 - Success
# 1 - General error
# 2 - Missing dependencies
################################################################################
# Enable strict error handling
set -euo pipefail
# Script configuration
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(dirname "$0")
readonly VERSION="1.0.0"
Error Handling and Validation
Implement robust error handling. Consequently, your scripts fail gracefully:
#!/bin/bash
set -euo pipefail # Exit on error, undefined variables, pipe failures
# Error trap
trap 'error_handler $? $LINENO' ERR
error_handler() {
local exit_code=$1
local line_number=$2
echo "Error: Script failed at line $line_number with exit code $exit_code" >&2
exit "$exit_code"
}
# Input validation
validate_directory() {
local dir=$1
if [ -z "$dir" ]; then
echo "Error: Directory path is empty" >&2
return 1
fi
if [ ! -d "$dir" ]; then
echo "Error: Directory does not exist: $dir" >&2
return 1
fi
if [ ! -r "$dir" ]; then
echo "Error: Directory is not readable: $dir" >&2
return 1
fi
return 0
}
# Usage
if validate_directory "/var/log"; then
echo "Directory is valid"
fi
Command-Line Argument Parsing
Parse arguments professionally using getopts:
#!/bin/bash
# Default values
VERBOSE=false
OUTPUT_FILE=""
CONFIG_FILE="/etc/myapp.conf"
# Usage function
usage() {
cat << EOF
Usage: $0 [OPTIONS]
Options:
-v, --verbose Enable verbose output
-o, --output FILE Output file path
-c, --config FILE Configuration file (default: $CONFIG_FILE)
-h, --help Display this help message
Examples:
$0 -v -o result.txt
$0 --config custom.conf --output data.out
EOF
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose)
VERBOSE=true
shift
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
-c|--config)
CONFIG_FILE="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo "Error: Unknown option: $1" >&2
echo "Use -h or --help for usage information"
exit 1
;;
esac
done
# Validate required arguments
if [ -z "$OUTPUT_FILE" ]; then
echo "Error: Output file is required (-o option)" >&2
exit 1
fi
# Use the arguments
if [ "$VERBOSE" = true ]; then
echo "Verbose mode enabled"
echo "Output file: $OUTPUT_FILE"
echo "Config file: $CONFIG_FILE"
fi
Security Considerations
Protect your scripts from common security issues:
#!/bin/bash
# 1. Always quote variables to prevent word splitting
file_path="/path/to/my file.txt"
if [ -f "$file_path" ]; then # GOOD
echo "File exists"
fi
# 2. Use absolute paths for commands
readonly RM_CMD="/bin/rm"
readonly TAR_CMD="/bin/tar"
$RM_CMD -f /tmp/tempfile
$TAR_CMD -czf backup.tar.gz /data
# 3. Sanitize user input
sanitize_input() {
local input=$1
# Remove potentially dangerous characters
echo "$input" | tr -cd '[:alnum:]._-'
}
user_input="file; rm -rf /"
safe_input=$(sanitize_input "$user_input")
echo "Sanitized: $safe_input"
# 4. Use readonly for constants
readonly MAX_RETRIES=3
readonly TIMEOUT=30
# 5. Avoid eval - use arrays instead
# BAD: eval "$dangerous_command"
# GOOD:
declare -a safe_commands=("ls" "-la" "/tmp")
"${safe_commands[@]}"
Troubleshooting Common Bash Script Errors
Even experienced scripters encounter errors. Therefore, understanding common issues helps debug scripts efficiently.
"command not found"
Symptom:
./script.sh: line 5: mycommand: command not found
Causes and Solutions:
- Missing executable in PATH:
# Check if command exists
which mycommand
# Use full path
/usr/local/bin/mycommand
# Or add to PATH
export PATH=$PATH:/usr/local/bin
- Typo in command name:
# Check spelling
pytohn script.py # WRONG
python script.py # CORRECT
- Missing shebang or wrong interpreter:
#!/bin/bash # Ensure this is first line
"Permission denied"
Symptom:
bash: ./script.sh: Permission denied
Solution:
# Make script executable
chmod +x script.sh
# Verify permissions
ls -l script.sh
# Alternative: run with bash explicitly
bash script.sh
"Syntax error near unexpected token"
Symptom:
./script.sh: line 10: syntax error near unexpected token `fi'
Common Causes:
- Missing keywords:
# WRONG
if [ $x -eq 1 ]; then
echo "x is 1"
# Missing 'fi'
# CORRECT
if [ $x -eq 1 ]; then
echo "x is 1"
fi
- Incorrect spacing in conditionals:
# WRONG
if [$x -eq 1]; then # No spaces inside brackets
# CORRECT
if [ $x -eq 1 ]; then # Spaces required
- Unmatched quotes:
# WRONG
echo "Hello World # Missing closing quote
# CORRECT
echo "Hello World"
"Unbound variable"
Symptom (when using set -u):
./script.sh: line 15: my_var: unbound variable
Solutions:
# Set default value
my_var=${my_var:-"default"}
# Check if variable is set
if [ -n "${my_var:-}" ]; then
echo "$my_var"
fi
# Use conditional assignment
: "${my_var:=default_value}"
Debugging Techniques
Enable debugging modes to trace execution:
#!/bin/bash
# Enable debug mode (print each command before execution)
set -x
# Enable verbose mode (print input lines)
set -v
# Disable debug mode
set +x
# Conditional debugging
DEBUG=${DEBUG:-false}
if [ "$DEBUG" = true ]; then
set -x
fi
# Debug specific section
echo "Starting critical section"
set -x
critical_command_1
critical_command_2
set +x
echo "Critical section completed"
Running scripts with debugging:
# Full script debugging
bash -x script.sh
# Debug with verbose output
bash -xv script.sh
# Check syntax without execution
bash -n script.sh
Common Logic Errors
File test mistakes:
# WRONG - Missing quotes
if [ -f $file ]; then # Fails if $file contains spaces
# CORRECT
if [ -f "$file" ]; then
# WRONG - Using -e for files
if [ -e "$file" ]; then # Matches directories too
# CORRECT - Use -f for regular files
if [ -f "$file" ]; then
Arithmetic comparison errors:
# WRONG - Using string comparison for numbers
if [ $num > 10 ]; then # String comparison, creates file "10"
# CORRECT - Use numeric comparison
if [ $num -gt 10 ]; then
Diagnostic Commands
Use these commands to diagnose issues:
# Check script syntax
bash -n script.sh
# View environment variables
printenv
env
# Trace system calls
strace -e trace=open,read,write ./script.sh
# Check which shell is being used
echo $SHELL
echo $0
# View bash version
bash --version
# Check if commands exist
command -v python
type python
which python
FAQ: Bash Scripting for Beginners
How do I start learning bash scripting?
Start with basic commands in the terminal, then create simple scripts combining those commands. Additionally, practice writing small automation scripts for tasks you do regularly. Resources like the GNU Bash Manual and Linux Documentation Project provide excellent learning materials.
What's the difference between sh and bash?
sh (Bourne Shell) is the original Unix shell with POSIX compliance. bash (Bourne Again Shell) is an enhanced version with additional features like arrays, extended pattern matching, and more built-in commands. Therefore, use #!/bin/bash for scripts requiring bash-specific features and #!/bin/sh for maximum portability.
How do I debug a bash script that isn't working?
Enable debugging with set -x at the start of your script or run it with bash -x script.sh. Furthermore, add echo statements to print variable values. Check syntax with bash -n script.sh before execution. Use shellcheck (available at ShellCheck.net) for static analysis.
Should I use single or double quotes in bash scripts?
Use double quotes when you need variable expansion: echo "$HOME". Use single quotes for literal strings where no expansion occurs: echo 'Cost: $100'. Additionally, always quote variables to prevent word splitting: [ -f "$file" ] not [ -f $file ].
How can I pass arguments to a bash script?
Access arguments using positional parameters: $1, $2, etc. The special variable $@ contains all arguments, and $# contains the count. Example:
#!/bin/bash
echo "First arg: $1"
echo "All args: $@"
echo "Number of args: $#"
What exit codes should my bash script return?
Return 0 for success and non-zero for errors. Conventionally: 1 for general errors, 2 for misuse of shell commands, 126 for command cannot execute, 127 for command not found. Moreover, define custom exit codes in your script header for specific error conditions.
How do I make my bash script run automatically?
Schedule scripts with cron for recurring tasks. Add entries to crontab with crontab -e. For system boot execution, use systemd services or add scripts to /etc/rc.local. Alternatively, use systemd timers for more complex scheduling needs. See systemd.io for modern scheduling methods.
Can bash scripts work on all Linux distributions?
Scripts using standard POSIX commands work across distributions. However, distribution-specific commands (like package managers: apt vs yum) require conditional logic. Furthermore, use #!/bin/sh for maximum portability or check the distribution with /etc/os-release.
How do I handle errors in bash scripts?
Use set -e to exit on errors, set -u for undefined variables, and set -o pipefail for pipe failures. Implement trap handlers for cleanup:
set -euo pipefail
trap 'echo "Error at line $LINENO"' ERR
Additionally, check exit codes with $? after critical commands.
What are the best resources for learning bash scripting?
Authoritative resources:
- GNU Bash Manual - Official documentation
- Advanced Bash-Scripting Guide - Comprehensive tutorial
- Bash Academy - Modern interactive guide
- ExplainShell - Command breakdown tool
- ShellCheck - Script analysis tool
Real-World Bash Scripting Example
Here's a complete, production-ready script demonstrating best practices:
#!/bin/bash
################################################################################
# Script: system_monitor.sh
# Description: Monitor system resources and send alerts
# Author: LinuxTips.pro
# Version: 1.0.0
################################################################################
set -euo pipefail
# Configuration
readonly DISK_THRESHOLD=85
readonly MEMORY_THRESHOLD=90
readonly CPU_THRESHOLD=80
readonly LOG_FILE="/var/log/system_monitor.log"
readonly ADMIN_EMAIL="admin@example.com"
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color
# Logging function
log() {
local level=$1
shift
local message="$@"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" | tee -a "$LOG_FILE"
}
# Check disk usage
check_disk() {
log "INFO" "Checking disk usage..."
while read -r line; do
usage=$(echo "$line" | awk '{print $5}' | sed 's/%//')
partition=$(echo "$line" | awk '{print $6}')
if [ "$usage" -ge "$DISK_THRESHOLD" ]; then
echo -e "${RED}β${NC} Disk usage critical on $partition: ${usage}%"
log "ERROR" "Disk usage critical on $partition: ${usage}%"
return 1
else
echo -e "${GREEN}β${NC} Disk usage healthy on $partition: ${usage}%"
fi
done < <(df -h | grep '^/dev/')
return 0
}
# Check memory usage
check_memory() {
log "INFO" "Checking memory usage..."
local mem_usage=$(free | grep Mem | awk '{printf("%.0f", $3/$2 * 100)}')
if [ "$mem_usage" -ge "$MEMORY_THRESHOLD" ]; then
echo -e "${RED}β${NC} Memory usage critical: ${mem_usage}%"
log "ERROR" "Memory usage critical: ${mem_usage}%"
return 1
else
echo -e "${GREEN}β${NC} Memory usage healthy: ${mem_usage}%"
fi
return 0
}
# Check CPU load
check_cpu() {
log "INFO" "Checking CPU load..."
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')
local cpu_int=${cpu_usage%.*}
if [ "$cpu_int" -ge "$CPU_THRESHOLD" ]; then
echo -e "${RED}β${NC} CPU usage critical: ${cpu_usage}%"
log "ERROR" "CPU usage critical: ${cpu_usage}%"
return 1
else
echo -e "${GREEN}β${NC} CPU usage healthy: ${cpu_usage}%"
fi
return 0
}
# Main function
main() {
echo "========================================="
echo " System Resource Monitor"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================="
echo ""
local exit_code=0
check_disk || exit_code=1
echo ""
check_memory || exit_code=1
echo ""
check_cpu || exit_code=1
echo ""
if [ $exit_code -eq 0 ]; then
echo -e "${GREEN}All checks passed!${NC}"
log "INFO" "All system checks passed"
else
echo -e "${RED}Some checks failed!${NC}"
log "ERROR" "System check failures detected"
fi
echo "========================================="
return $exit_code
}
# Run main function
main "$@"
exit $?
Conclusion
Bash scripting for beginners opens the door to powerful Linux automation. Throughout this guide, we've covered essential concepts from basic script structure to advanced control flow. Furthermore, you've learned variable handling, conditional logic, loops, and functionsβthe building blocks of effective scripts.
Remember these key takeaways:
- Always start scripts with proper shebangs (
#!/bin/bash) - Quote variables to prevent word splitting errors
- Implement error handling with
set -euo pipefail - Document your code with clear comments and headers
- Test scripts thoroughly before production use
Moreover, continue learning by reading official documentation, analyzing existing scripts, and practicing regularly. The Linux Foundation offers excellent resources for deepening your Linux expertise.
Next Steps:
- Practice writing scripts for your daily tasks
- Study the GNU Bash Manual
- Explore advanced topics like process management and signal handling
- Contribute to open-source projects to learn from experienced developers
- Join Linux communities on Stack Overflow and Reddit r/bash
Start automating todayβyour future self will thank you for the time saved!
Additional Resources
Official Documentation:
- GNU Bash Manual - Comprehensive bash documentation
- Bash Reference Manual - Official GNU reference
- Linux man pages - Command documentation
Learning Platforms:
- Linux Journey - Interactive Linux learning
- Bash Academy - Modern bash tutorial
- Advanced Bash-Scripting Guide - In-depth guide
Tools:
- ShellCheck - Script linting and analysis
- ExplainShell - Command breakdown tool
- Bash-it - Bash framework and themes
Community:
Author: LinuxTips.pro Editorial Team
Last Updated: January 7, 2025
Article Series: Linux Mastery 100 - Post #31
Reading Time: 25 minutes
Master Linux systematically with our comprehensive 100-topic series.