How to use variables in Bash scripts
How to Use Variables in Bash Scripts
Variables are fundamental building blocks in Bash scripting that allow you to store, manipulate, and retrieve data throughout your scripts. Whether you're automating system tasks, processing files, or building complex workflows, understanding how to effectively use variables is essential for creating robust and maintainable Bash scripts. This comprehensive guide will take you from basic variable concepts to advanced techniques used by professional system administrators and developers.
Table of Contents
1. [Prerequisites and Requirements](#prerequisites-and-requirements)
2. [Understanding Bash Variables](#understanding-bash-variables)
3. [Variable Declaration and Assignment](#variable-declaration-and-assignment)
4. [Variable Types and Data Handling](#variable-types-and-data-handling)
5. [Variable Scope and Environment](#variable-scope-and-environment)
6. [Advanced Variable Techniques](#advanced-variable-techniques)
7. [Practical Examples and Use Cases](#practical-examples-and-use-cases)
8. [Common Issues and Troubleshooting](#common-issues-and-troubleshooting)
9. [Best Practices and Professional Tips](#best-practices-and-professional-tips)
10. [Conclusion and Next Steps](#conclusion-and-next-steps)
Prerequisites and Requirements
Before diving into Bash variables, ensure you have:
- Basic understanding of command-line interfaces
- Access to a Unix-like system (Linux, macOS, or WSL on Windows)
- Bash shell version 3.0 or higher (check with `bash --version`)
- Text editor for writing scripts (nano, vim, VS Code, etc.)
- Basic familiarity with file permissions and script execution
Setting Up Your Environment
Create a dedicated directory for practicing Bash scripts:
```bash
mkdir ~/bash-practice
cd ~/bash-practice
```
Ensure your scripts are executable by setting proper permissions:
```bash
chmod +x your-script.sh
```
Understanding Bash Variables
What Are Bash Variables?
Bash variables are named storage locations that hold data values. Unlike strongly-typed programming languages, Bash treats all variables as strings by default, but can perform arithmetic operations when needed. Variables provide a way to:
- Store user input and command output
- Pass data between functions and scripts
- Configure script behavior
- Maintain state throughout script execution
Variable Naming Conventions
Bash variable names must follow specific rules:
- Start with a letter or underscore
- Contain only letters, numbers, and underscores
- Case-sensitive (NAME and name are different)
- Cannot contain spaces or special characters
Valid variable names:
```bash
username
_temp_file
USER_HOME
file2process
```
Invalid variable names:
```bash
2username # Cannot start with number
user-name # Hyphens not allowed
user name # Spaces not allowed
user@home # Special characters not allowed
```
Variable Declaration and Assignment
Basic Variable Assignment
The simplest way to create a variable is direct assignment:
```bash
#!/bin/bash
Basic string assignment
name="John Doe"
city="New York"
Numeric assignment (stored as string)
age=30
count=0
Empty variable
empty_var=""
echo "Name: $name"
echo "City: $city"
echo "Age: $age"
```
Important: No spaces around the equals sign. `name = "John"` will cause an error.
Using Variables
Access variable values using the dollar sign (`$`) prefix:
```bash
#!/bin/bash
message="Hello World"
Method 1: Simple variable expansion
echo $message
Method 2: Braced variable expansion (recommended)
echo ${message}
Method 3: Within double quotes
echo "The message is: $message"
Method 4: Concatenation
greeting="$message from Bash!"
echo $greeting
```
Command Substitution
Capture command output in variables:
```bash
#!/bin/bash
Method 1: Using backticks (older syntax)
current_date=`date`
Method 2: Using $() (preferred)
current_user=$(whoami)
file_count=$(ls -1 | wc -l)
system_info=$(uname -a)
echo "Date: $current_date"
echo "User: $current_user"
echo "Files in directory: $file_count"
echo "System: $system_info"
```
Read-Only Variables
Create constants using the `readonly` command:
```bash
#!/bin/bash
readonly PI=3.14159
readonly APP_NAME="MyApplication"
echo "Pi value: $PI"
This will cause an error
PI=3.14 # bash: PI: readonly variable
```
Variable Types and Data Handling
String Variables
Bash excels at string manipulation:
```bash
#!/bin/bash
String assignment with different quote types
single_quoted='This is a literal string with $variables not expanded'
double_quoted="This string expands $USER variable"
no_quotes=simple_string_without_spaces
Multi-line strings
multi_line="Line 1
Line 2
Line 3"
Escape sequences in double quotes
escaped_string="Path: /home/user\nNew line here"
echo "Single quoted: $single_quoted"
echo "Double quoted: $double_quoted"
echo "No quotes: $no_quotes"
echo -e "Multi-line:\n$multi_line"
echo -e "Escaped: $escaped_string"
```
Numeric Variables and Arithmetic
Perform arithmetic operations using various methods:
```bash
#!/bin/bash
Basic arithmetic with $(())
num1=10
num2=5
sum=$((num1 + num2))
difference=$((num1 - num2))
product=$((num1 * num2))
quotient=$((num1 / num2))
remainder=$((num1 % num2))
echo "Sum: $sum"
echo "Difference: $difference"
echo "Product: $product"
echo "Quotient: $quotient"
echo "Remainder: $remainder"
Increment and decrement
counter=0
((counter++)) # Increment by 1
echo "Counter after increment: $counter"
((counter += 5)) # Add 5
echo "Counter after adding 5: $counter"
Using let command
let result=num1*num2+10
echo "Let result: $result"
```
Array Variables
Bash supports indexed and associative arrays:
Indexed Arrays
```bash
#!/bin/bash
Array declaration and assignment
fruits=("apple" "banana" "cherry" "date")
Alternative assignment methods
colors[0]="red"
colors[1]="green"
colors[2]="blue"
Adding elements
fruits+=("elderberry")
Accessing array elements
echo "First fruit: ${fruits[0]}"
echo "Third color: ${colors[2]}"
Array length
echo "Number of fruits: ${#fruits[@]}"
All array elements
echo "All fruits: ${fruits[@]}"
echo "All colors: ${colors[*]}"
Iterating through array
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
```
Associative Arrays
```bash
#!/bin/bash
Declare associative array
declare -A person
Assign values
person[name]="Alice"
person[age]=25
person[city]="Boston"
person[occupation]="Engineer"
Access values
echo "Name: ${person[name]}"
echo "Age: ${person[age]}"
Get all keys
echo "Keys: ${!person[@]}"
Get all values
echo "Values: ${person[@]}"
Iterate through associative array
for key in "${!person[@]}"; do
echo "$key: ${person[$key]}"
done
```
Variable Scope and Environment
Local vs Global Variables
Understanding variable scope is crucial for complex scripts:
```bash
#!/bin/bash
Global variable
global_var="I'm global"
function demo_scope() {
# Local variable (only accessible within function)
local local_var="I'm local"
# Modify global variable
global_var="Modified global"
echo "Inside function:"
echo " Global: $global_var"
echo " Local: $local_var"
}
echo "Before function call:"
echo " Global: $global_var"
demo_scope
echo "After function call:"
echo " Global: $global_var"
echo " Local: $local_var" # This will be empty
```
Environment Variables
Work with system environment variables:
```bash
#!/bin/bash
Display common environment variables
echo "User: $USER"
echo "Home: $HOME"
echo "Path: $PATH"
echo "Shell: $SHELL"
Set environment variable for child processes
export MY_APP_CONFIG="/etc/myapp/config.conf"
Check if environment variable exists
if [ -n "$EDITOR" ]; then
echo "Default editor: $EDITOR"
else
echo "No default editor set"
fi
Set default value if variable is unset
database_host=${DB_HOST:-"localhost"}
echo "Database host: $database_host"
```
Variable Export and Inheritance
```bash
#!/bin/bash
Local variable (not inherited by child processes)
local_setting="local_value"
Export variable to make it available to child processes
export GLOBAL_SETTING="global_value"
Create a child script to test inheritance
cat > child_script.sh << 'EOF'
#!/bin/bash
echo "In child script:"
echo " Local setting: $local_setting"
echo " Global setting: $GLOBAL_SETTING"
EOF
chmod +x child_script.sh
./child_script.sh
Clean up
rm child_script.sh
```
Advanced Variable Techniques
Parameter Expansion
Bash provides powerful parameter expansion features:
```bash
#!/bin/bash
filename="document.txt"
filepath="/home/user/documents/report.pdf"
String length
echo "Length of filename: ${#filename}"
Substring extraction
echo "First 3 characters: ${filename:0:3}"
echo "From 4th character: ${filename:3}"
echo "Last 3 characters: ${filename: -3}"
Pattern matching and replacement
echo "Replace .txt with .bak: ${filename/txt/bak}"
echo "Remove extension: ${filename%.*}"
echo "Get directory: ${filepath%/*}"
echo "Get basename: ${filepath##*/}"
Case modification (Bash 4+)
text="Hello World"
echo "Uppercase: ${text^^}"
echo "Lowercase: ${text,,}"
echo "Capitalize first: ${text^}"
```
Default Values and Error Handling
```bash
#!/bin/bash
Set default values
username=${1:-"anonymous"}
config_file=${CONFIG_FILE:-"/etc/default.conf"}
Use alternative value if variable is unset or empty
database_url=${DB_URL:-"sqlite:///default.db"}
Exit with error if required variable is unset
required_api_key=${API_KEY:?"API_KEY environment variable is required"}
Assign default and export if unset
: ${LOG_LEVEL:="INFO"}
export LOG_LEVEL
echo "Username: $username"
echo "Config file: $config_file"
echo "Database URL: $database_url"
echo "Log level: $LOG_LEVEL"
```
Variable Validation and Type Checking
```bash
#!/bin/bash
validate_number() {
local value=$1
if [[ $value =~ ^[0-9]+$ ]]; then
echo "$value is a valid number"
return 0
else
echo "$value is not a valid number"
return 1
fi
}
validate_email() {
local email=$1
local pattern="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if [[ $email =~ $pattern ]]; then
echo "$email is a valid email"
return 0
else
echo "$email is not a valid email"
return 1
fi
}
Test validation functions
test_number="123"
test_email="user@example.com"
validate_number "$test_number"
validate_email "$test_email"
```
Practical Examples and Use Cases
System Information Script
```bash
#!/bin/bash
System monitoring script using variables
script_name=$(basename "$0")
timestamp=$(date "+%Y-%m-%d %H:%M:%S")
hostname=$(hostname)
uptime_info=$(uptime | awk '{print $3,$4}' | sed 's/,//')
disk_usage=$(df -h / | awk 'NR==2 {print $5}')
memory_usage=$(free | awk 'NR==2{printf "%.2f%%", $3*100/$2}')
cpu_load=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')
Generate report
report_file="system_report_$(date +%Y%m%d_%H%M%S).txt"
cat > "$report_file" << EOF
=================================
System Report - $script_name
=================================
Generated: $timestamp
Hostname: $hostname
Uptime: $uptime_info
Disk Usage (root): $disk_usage
Memory Usage: $memory_usage
CPU Load: $cpu_load
Report saved to: $report_file
EOF
echo "System report generated: $report_file"
cat "$report_file"
```
Configuration File Parser
```bash
#!/bin/bash
Configuration file parser
config_file="app.conf"
Create sample configuration file
cat > "$config_file" << EOF
Application Configuration
app_name=MyApplication
app_version=1.2.3
debug_mode=true
database_host=localhost
database_port=5432
database_name=myapp_db
max_connections=100
EOF
Function to read configuration
read_config() {
local config_file=$1
if [ ! -f "$config_file" ]; then
echo "Configuration file not found: $config_file"
return 1
fi
# Read configuration variables
while IFS='=' read -r key value; do
# Skip comments and empty lines
[[ $key =~ ^[[:space:]]*# ]] && continue
[[ -z $key ]] && continue
# Remove leading/trailing whitespace
key=$(echo "$key" | xargs)
value=$(echo "$value" | xargs)
# Set variable dynamically
declare -g "$key=$value"
echo "Loaded: $key=$value"
done < "$config_file"
}
Load configuration
echo "Loading configuration from $config_file..."
read_config "$config_file"
Use configuration variables
echo ""
echo "Application: $app_name v$app_version"
echo "Database: $database_host:$database_port/$database_name"
echo "Debug mode: $debug_mode"
echo "Max connections: $max_connections"
Clean up
rm "$config_file"
```
Backup Script with Variables
```bash
#!/bin/bash
Automated backup script
source_dir="${1:-$HOME/Documents}"
backup_base_dir="${BACKUP_DIR:-$HOME/backups}"
timestamp=$(date "+%Y%m%d_%H%M%S")
backup_name="backup_$timestamp"
backup_dir="$backup_base_dir/$backup_name"
log_file="$backup_base_dir/backup.log"
max_backups=5
Create backup directory
mkdir -p "$backup_dir"
mkdir -p "$backup_base_dir"
Logging function
log_message() {
local message=$1
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$timestamp] $message" | tee -a "$log_file"
}
Start backup process
log_message "Starting backup of $source_dir"
log_message "Backup destination: $backup_dir"
Perform backup
if cp -r "$source_dir"/* "$backup_dir/" 2>/dev/null; then
backup_size=$(du -sh "$backup_dir" | cut -f1)
log_message "Backup completed successfully. Size: $backup_size"
else
log_message "Backup failed!"
exit 1
fi
Cleanup old backups
log_message "Cleaning up old backups (keeping last $max_backups)"
cd "$backup_base_dir"
ls -t | grep "^backup_" | tail -n +$((max_backups + 1)) | xargs rm -rf
Summary
remaining_backups=$(ls -1 | grep "^backup_" | wc -l)
log_message "Cleanup completed. Remaining backups: $remaining_backups"
log_message "Backup process finished"
```
Common Issues and Troubleshooting
Variable Assignment Errors
Problem: Spaces around equals sign
```bash
Wrong
name = "John" # Error: command not found
Correct
name="John"
```
Problem: Unquoted variables with spaces
```bash
Wrong
file_name=my file.txt # Error: command not found
Correct
file_name="my file.txt"
```
Variable Expansion Issues
Problem: Unquoted variable expansion
```bash
#!/bin/bash
files="file1.txt file2.txt file3.txt"
Wrong - word splitting occurs
for file in $files; do
echo $file
done
Correct - preserve as single string when needed
for file in "$files"; do
echo "$file"
done
Or split intentionally
for file in ${files}; do
echo "$file"
done
```
Problem: Variable not found errors
```bash
#!/bin/bash
Check if variable exists before using
if [ -z "$UNDEFINED_VAR" ]; then
echo "Variable is not set or empty"
fi
Use default values
safe_var=${UNDEFINED_VAR:-"default_value"}
echo "Safe variable: $safe_var"
```
Array Issues
Problem: Incorrect array access
```bash
#!/bin/bash
arr=("one" "two" "three")
Wrong - only gets first element
echo "Wrong: $arr"
Correct - gets specific element
echo "Correct element: ${arr[1]}"
Correct - gets all elements
echo "All elements: ${arr[@]}"
```
Scope and Export Problems
Problem: Variables not available in child processes
```bash
#!/bin/bash
This won't be available to child processes
local_var="local_value"
This will be available
export global_var="global_value"
Test with subshell
(
echo "In subshell:"
echo "Local: $local_var" # Empty
echo "Global: $global_var" # Has value
)
```
Debugging Variable Issues
Use these techniques to debug variable problems:
```bash
#!/bin/bash
Enable debugging
set -x # Print commands as they execute
set -u # Exit on undefined variables
set -e # Exit on any error
Debug specific variables
debug_var() {
local var_name=$1
local var_value=${!var_name}
echo "DEBUG: $var_name='$var_value' (length: ${#var_value})"
}
Example usage
test_var="hello world"
debug_var test_var
Disable debugging
set +x
```
Best Practices and Professional Tips
Naming Conventions
Follow consistent naming conventions:
```bash
#!/bin/bash
Constants in UPPERCASE
readonly MAX_RETRIES=3
readonly CONFIG_DIR="/etc/myapp"
Regular variables in lowercase with underscores
user_input=""
file_count=0
backup_directory=""
Function names in lowercase with underscores
process_file() {
local file_path=$1
# Function implementation
}
Environment variables in UPPERCASE
export APP_DEBUG_MODE="true"
export DATABASE_URL="postgresql://localhost/mydb"
```
Variable Initialization
Always initialize variables:
```bash
#!/bin/bash
Initialize variables with appropriate defaults
count=0
message=""
status="unknown"
files=()
declare -A config
Check for required variables
required_vars=("USER" "HOME" "PATH")
for var in "${required_vars[@]}"; do
if [ -z "${!var}" ]; then
echo "Error: Required variable $var is not set"
exit 1
fi
done
```
Secure Variable Handling
Handle sensitive data securely:
```bash
#!/bin/bash
Read sensitive data without echoing
read -s -p "Enter password: " password
echo # New line after hidden input
Clear sensitive variables after use
process_with_password() {
local temp_password=$1
# Use password for processing
echo "Processing with credentials..."
# Clear from memory
temp_password=""
}
process_with_password "$password"
password="" # Clear original variable
```
Performance Optimization
Optimize variable usage for better performance:
```bash
#!/bin/bash
Cache expensive operations
if [ -z "$CACHED_HOSTNAME" ]; then
CACHED_HOSTNAME=$(hostname -f)
export CACHED_HOSTNAME
fi
Use local variables in functions
process_data() {
local input_file=$1
local output_file=$2
local temp_file="/tmp/process_$$"
# Process data using local variables
# This prevents global namespace pollution
}
Avoid unnecessary subshells for simple operations
Instead of: result=$(echo $var | tr '[:lower:]' '[:upper:]')
Use: result=${var^^} # Bash 4+ parameter expansion
```
Error Handling and Validation
Implement robust error handling:
```bash
#!/bin/bash
Function to validate required variables
validate_required_vars() {
local vars=("$@")
local missing=()
for var in "${vars[@]}"; do
if [ -z "${!var:-}" ]; then
missing+=("$var")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
echo "Error: Missing required variables: ${missing[*]}" >&2
return 1
fi
return 0
}
Usage example
API_ENDPOINT="https://api.example.com"
API_KEY="" # Intentionally left unset for demo
if validate_required_vars "API_ENDPOINT" "API_KEY"; then
echo "All required variables are set"
else
echo "Cannot proceed without required variables"
exit 1
fi
```
Documentation and Comments
Document your variables effectively:
```bash
#!/bin/bash
#==============================================================================
Script: data_processor.sh
Description: Process data files with configurable parameters
Author: Your Name
Version: 1.0
#==============================================================================
Configuration variables
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
readonly LOG_FILE="${LOG_DIR:-/tmp}/${SCRIPT_NAME%.*}.log"
Processing parameters
MAX_FILE_SIZE=${MAX_FILE_SIZE:-1048576} # Default: 1MB in bytes
BATCH_SIZE=${BATCH_SIZE:-100} # Default: 100 files per batch
TIMEOUT=${TIMEOUT:-30} # Default: 30 seconds
Runtime variables
declare -i processed_count=0 # Counter for processed files
declare -a failed_files=() # Array of files that failed processing
declare -A file_stats=() # Associative array for statistics
Function variables (document complex functions)
process_batch() {
local -r batch_dir=$1 # Input: directory containing batch
local -r output_dir=$2 # Input: output directory
local -i batch_count=0 # Local: counter for current batch
# Function implementation...
}
```
Conclusion and Next Steps
Mastering variables in Bash scripts is essential for creating powerful, maintainable automation tools. This comprehensive guide has covered everything from basic variable assignment to advanced techniques used in professional environments.
Key Takeaways
1. Proper Syntax: Always remember no spaces around the equals sign in assignments
2. Quoting: Use double quotes for variable expansion and single quotes for literals
3. Scope Management: Understand the difference between local and global variables
4. Parameter Expansion: Leverage Bash's powerful parameter expansion features
5. Error Handling: Always validate and handle variables safely
6. Performance: Use appropriate techniques for optimal script performance
Next Steps for Continued Learning
1. Advanced Scripting: Explore functions, loops, and conditional statements
2. Regular Expressions: Learn pattern matching for advanced string manipulation
3. Process Management: Study job control and process handling
4. Debugging Techniques: Master tools like `bash -x` and `shellcheck`
5. Security Practices: Learn about secure scripting practices and input validation
Additional Resources
- Bash manual: `man bash`
- Online Bash guide: Advanced Bash-Scripting Guide
- Linting tool: ShellCheck for script validation
- Testing framework: Bats for Bash script testing
Practice Exercises
To reinforce your learning, try these exercises:
1. Create a script that manages user accounts with variables for username, home directory, and shell
2. Build a log analyzer that uses arrays to store and process log entries
3. Develop a configuration management script that reads from files and environment variables
4. Write a backup script with comprehensive error handling and variable validation
By mastering these variable techniques, you'll be well-equipped to create sophisticated Bash scripts that are both powerful and maintainable. Remember to always test your scripts thoroughly and follow best practices for security and performance.