Variables
Chapter 4: Variables and Parameters
Section titled “Chapter 4: Variables and Parameters”Overview
Section titled “Overview”Variables are the foundation of any programming language, and bash is no exception. In this chapter, we’ll explore variables in depth, including variable types, declaration, scope, and special parameters. This knowledge is essential for writing robust bash scripts used in DevOps, SRE, and System Administration.
Variable Basics
Section titled “Variable Basics”What Are Variables?
Section titled “What Are Variables?”A variable is a named storage location that holds a value. In bash, variables are dynamically typed and don’t need explicit type declaration.
┌─────────────────────────────────────────────────────────────────────┐│ VARIABLE CONCEPTS │└─────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐│ VARIABLE STRUCTURE ││ ───────────────── ││ ││ ┌─────────────────┐ ┌──────────────────────────────┐ ││ │ Variable Name │ ────▶│ Stored Value │ ││ │ │ │ │ ││ │ SERVER_NAME │ │ "production-web-01" │ ││ │ PORT │ │ 8080 │ ││ │ DB_HOST │ │ "db.example.com" │ ││ │ MAX_RETRIES │ │ 3 │ ││ └─────────────────┘ └──────────────────────────────┘ ││ ││ Variable = Name + Value + Type (implicit) │└───────────────────────────────────────────────────────────────────────┘Variable Naming Rules
Section titled “Variable Naming Rules”# Valid variable namesname="John"server_name="prod-server"PORT=8080dbHost="localhost"MAX_RETRIES=3_private_var="hidden"MY_VAR_123="allowed"
# Invalid variable names (will cause errors)# 123variable="starts with number"# my-var="contains hyphen"# my.var="contains dot"# my var="contains space"# if="reserved keyword"Case Sensitivity
Section titled “Case Sensitivity”# Bash variables are case-sensitiveNAME="John"name="Jane"Name="Mike"
echo "NAME=$NAME" # NAME=Johnecho "name=$name" # name=Janeecho "Name=$Name" # Name=Mike
# Convention: Use UPPERCASE for constants, lowercase for variables# This is a common convention, not a requirementDeclaring Variables
Section titled “Declaring Variables”Basic Assignment
Section titled “Basic Assignment”# String assignment (no spaces around =)greeting="Hello"message='Welcome to Bash Scripting'
# Number assignment (stored as string internally)count=42price=19.99
# Empty variableempty_var=# orempty_var=""
# Command output assignmentcurrent_date=$(date)file_count=$(ls -1 | wc -l)disk_usage=$(df -h / | tail -1 | awk '{print $5}')declare Command
Section titled “declare Command”The declare command is used to give attributes to variables.
# Declare integer variabledeclare -i age=25age="30" # Automatically converted to integer
# Declare readonly variabledeclare -r CONSTANT="immutable"# CONSTANT="changed" # Error: readonly variable
# Declare arraydeclare -a fruits=("apple" "banana" "cherry")
# Declare associative array (bash 4+)declare -A user_infouser_info["name"]="John"user_info["email"]="john@example.com"
# Declare export variabledeclare -x PATH_VAR="/usr/local/bin"
# Show variable attributesdeclare -p variable_namedeclare -p age# Output: declare -i age="30"
# List all variables with attributesdeclaretypeset Command
Section titled “typeset Command”typeset is an alias for declare in bash. It’s more portable to other shells.
# Same as declaretypeset -i counter=10typeset -r CONST="value"
# Use typeset for better portabilitytypeset -A config # Associative arrayAccessing Variables
Section titled “Accessing Variables”Using $ or ${}
Section titled “Using $ or ${}”# Basic access with $echo $nameecho $PORT
# Recommended: Use ${} for clarity and to avoid ambiguityecho ${name}echo ${PORT}
# When to use ${}:# - At the end of a variable, both work# - When concatenating with other text, ${} is required
# This works:echo "Hello $name"
# This works:echo "Hello ${name}"
# This FAILS (ambiguous):# echo "Hello $name_suffix" # Tries to find $name_suffix
# This WORKS:# echo "Hello ${name}_suffix" # Finds $name and appends _suffix
# Examples:server="production"echo "Server: ${server}-01"echo "Port: ${PORT}8080"Default Values
Section titled “Default Values”Bash provides powerful parameter expansion for handling defaults.
# Use default value if variable is unset or nullname=""echo "Hello ${name:-Guest}" # Output: Hello Guest# Note: name is still empty after this
# Assign default value if variable is unset or nullname=""echo "Hello ${name:=Guest}" # Output: Hello Guestecho "name is now: $name" # Output: name is now: Guest
# Use alternate value if variable is setname="John"echo "Hello ${name:+User}" # Output: Hello User (uses alternate)
# Show error if variable is unset# set -u is usually better (see below)# ${name:?Error message}Variable Expansion Patterns
Section titled “Variable Expansion Patterns”# Get length of variabletext="Hello World"echo ${#text} # Output: 11
# Substring extractiontext="Hello World"echo ${text:0:5} # Output: Hello (from position 0, length 5)echo ${text:6:5} # Output: World (from position 6, length 5)echo ${text:-5} # Output: World (last 5 characters)
# String removal (pattern matching)filename="report-2024-01-15.csv"echo ${filename#*.} # csv (remove shortest match from start)echo ${filename##*.} # csv (remove longest match from start)echo ${filename%.*} # report-2024-01-15 (remove shortest from end)echo ${filename%%.*} # report (remove longest from end)
# Search and replacetext="Hello World"echo ${text/World/Universe} # Hello Universe (first match)echo ${text//o/O} # HellO WOrld (all matches)echo ${text/#Hello/Hi} # Hi World (if starts with Hello)echo ${text/%World/Everyone} # Hello Everyone (if ends with World)
# Case modification (bash 4+)text="Hello World"echo ${text^} # Hello World (first char uppercase)echo ${text^^} # HELLO WORLD (all uppercase)echo ${text,} # hello World (first char lowercase)echo ${text,,} # hello world (all lowercase)Special Parameters
Section titled “Special Parameters”Bash provides special parameters that have special meaning.
┌─────────────────────────────────────────────────────────────────────┐│ SPECIAL PARAMETERS │└─────────────────────────────────────────────────────────────────────┘
┌──────────────────┬──────────────────────────────────────────────────┐│ Parameter │ Description │├──────────────────┼──────────────────────────────────────────────────┤│ $0 │ Script name ││ $1-$9 │ Positional parameters (1st-9th argument) ││ ${10}+ │ Positional parameters (10th+ arguments) ││ $# │ Number of positional parameters ││ $@ │ All positional parameters (as separate words) ││ $* │ All positional parameters (as single word) ││ $? │ Exit status of last command ││ $$ │ Process ID of current shell ││ $! │ Process ID of last background job ││ $_ │ Last argument to previous command ││ $- │ Current shell options (set flags) ││ $BASH_VERSION │ Bash version string ││ $BASH_SOURCE │ Source file path ││ $FUNCNAME │ Current function name (array) ││ $LINENO │ Current line number ││ $SECONDS │ Seconds since shell started ││ $RANDOM │ Random integer │└──────────────────┴──────────────────────────────────────────────────┘Positional Parameters
Section titled “Positional Parameters”#!/usr/bin/env bashecho "Script name: $0"echo "First argument: $1"echo "Second argument: $2"echo "Third argument: $3"
echo "Total arguments: $#"echo "All arguments: $@"echo "All arguments: $*"
# Arguments from 10 onwards need bracesecho "10th argument: ${10}"Example: Processing Arguments
Section titled “Example: Processing Arguments”#!/usr/bin/env bash# Check minimum argumentsif [ $# -lt 2 ]; then echo "Usage: $0 <source> <destination>" exit 1fi
SOURCE="$1"DESTINATION="$2"
echo "Copying from $SOURCE to $DESTINATION"cp -r "$SOURCE" "$DESTINATION"
echo "Done! Copied $# arguments"$@ vs $*
Section titled “$@ vs $*”#!/usr/bin/env bash# Both $@ and $* expand to all arguments# But they behave differently when quoted
# With double quotes:# "$@" → Each argument as separate quoted string# "$*" → All arguments as single quoted string
# Example:set -- "arg one" "arg two" "arg three"
echo "Using \$@:"for arg in "$@"; do echo " [$arg]"done
echo ""echo "Using \$*:"for arg in "$*"; do echo " [$arg]"done
# Output:# Using $@:# [arg one]# [arg two]# [arg three]## Using $*:# [arg one arg two arg three]$? - Exit Status
Section titled “$? - Exit Status”# Check exit status of last commandls /tmpecho "Exit status: $?" # 0 (success)
ls /nonexistentecho "Exit status: $?" # 2 (error)
# Use in conditionalsif grep -q "pattern" file.txt; then echo "Pattern found"else echo "Pattern not found"fi$$ - Process ID
Section titled “$$ - Process ID”# Current shell process IDecho "Current shell PID: $$"
# Useful for creating unique temporary filestemp_file="/tmp/output_$$.txt"echo "Temp file: $temp_file"
# In scripts, this is the script's PID, not parent shell$! - Background Job PID
Section titled “$! - Background Job PID”# Start a background processsleep 10 &echo "Background job PID: $!"
# Capture the PID for later uselong_running_command &BG_PID=$!echo "Started job with PID: $BG_PID"
# Later, you can check if it's still runningif kill -0 $BG_PID 2>/dev/null; then echo "Job is still running"else echo "Job completed"fiEnvironment Variables
Section titled “Environment Variables”What Are Environment Variables?
Section titled “What Are Environment Variables?”Environment variables are variables that are available to all child processes spawned by the shell. They carry information about the system and user preferences.
┌─────────────────────────────────────────────────────────────────────┐│ ENVIRONMENT VARIABLES │└─────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐│ SYSTEM ENVIRONMENT FLOW ││ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ System │ ────▶│ Shell │ ────▶│ Child │ ││ │ (init) │ │ (bash) │ │ Processes │ ││ │ │ │ │ │ │ ││ │ env vars │ ────▶│ + shell │ ────▶│ + inherited│ ││ │ │ │ vars │ │ env vars │ ││ └─────────────┘ └─────────────┘ └─────────────┘ ││ ││ Child processes inherit environment but NOT shell variables │└───────────────────────────────────────────────────────────────────────┘Common Environment Variables
Section titled “Common Environment Variables”# User informationecho "User: $USER"echo "UID: $UID"echo "Home: $HOME"echo "Shell: $SHELL"
# System informationecho "Hostname: $HOSTNAME"echo "OS: $OSTYPE"echo "Architecture: $HOSTTYPE"
# Pathsecho "PATH: $PATH"echo "Current dir: $PWD"echo "Previous dir: $OLDPWD"
# Localeecho "Language: $LANG"echo "LC_ALL: $LC_ALL"
# Editorecho "Editor: $EDITOR"echo "PAGER: $PAGER"
# Locale settingsecho "Home: $HOME"echo "Username: $LOGNAME"Setting Environment Variables
Section titled “Setting Environment Variables”# Export variable to make it available to child processesexport MY_VAR="value"MY_VAR="value"export MY_VAR
# Or combine:export MY_VAR="value"
# Show exported variablesexport -p
# Remove variableunset MY_VARDifference Between Shell and Environment Variables
Section titled “Difference Between Shell and Environment Variables”# Shell variable (not exported)MY_VAR="shell only"echo $MY_VARbash -c 'echo $MY_VAR' # Nothing (not inherited)
# Environment variable (exported)export MY_VAR="environment"echo $MY_VARbash -c 'echo $MY_VAR' # Outputs: environmentVariables in Scripts
Section titled “Variables in Scripts”Variable Scope in Functions
Section titled “Variable Scope in Functions”#!/usr/bin/env bash# Global variableGLOBAL_VAR="I'm global"
test_function() { # Access global variable echo "Inside function, GLOBAL_VAR = $GLOBAL_VAR"
# Local variable (only exists in this function) local LOCAL_VAR="I'm local" echo "Inside function, LOCAL_VAR = $LOCAL_VAR"
# Modify global variable GLOBAL_VAR="Modified global"}
echo "Before function: GLOBAL_VAR = $GLOBAL_VAR"test_functionecho "After function: GLOBAL_VAR = $GLOBAL_VAR"
# This will fail - LOCAL_VAR doesn't exist outside function# echo $LOCAL_VAR # ErrorPassing Variables to Functions
Section titled “Passing Variables to Functions”#!/usr/bin/env bash# Pass by value (default)greet() { local name="$1" echo "Hello, $name!" name="Modified" # Only modifies local copy}
user="Alice"greet "$user"echo "After function: $user" # Still "Alice"
# Pass by reference (using nameref - bash 4.3+)modify() { local -n ref="$1" ref="Modified value"}
original="Original value"modify originalecho "$original" # Outputs: Modified valueParameter Expansion in Practice
Section titled “Parameter Expansion in Practice”Real-World Examples
Section titled “Real-World Examples”# 1. Default configuration valuesCONFIG_DIR="${CONFIG_DIR:-/etc/myapp}"LOG_FILE="${LOG_FILE:-/var/log/myapp.log}"MAX_CONNECTIONS="${MAX_CONNECTIONS:-100}"
# 2. Setting defaults for optional argumentsINPUT_FILE="${1:-input.txt}"OUTPUT_FILE="${2:-output.txt}"
# 3. Using pattern matching for file extensionsfile="document.pdf"extension="${file##*.}" # pdfbasename="${file%.*}" # document
# 4. Safe variable accessUSERNAME="${USER:-unknown}"# Instead of: echo $USER (might be empty)
# 5. Mandatory variables checkREQUIRED_VAR="${REQUIRED_VAR:?Error: REQUIRED_VAR must be set}"
# 6. Case transformation for consistencyinput="HeLLo"normalized="${input,,}" # helloUPPERCASE="${input^^}" # HELLOArrays (Indexed)
Section titled “Arrays (Indexed)”Declaring Arrays
Section titled “Declaring Arrays”# Method 1: Direct assignmentfruits=("apple" "banana" "cherry")
# Method 2: Index assignmentcolors[0]="red"colors[1]="green"colors[2]="blue"
# Method 3: Using declaredeclare -a numbers=(1 2 3 4 5)
# Method 4: Empty arrayempty=()Accessing Array Elements
Section titled “Accessing Array Elements”fruits=("apple" "banana" "cherry" "date")
# First elementecho ${fruits[0]} # apple
# Last elementecho ${fruits[-1]} # dateecho ${fruits[-2]} # cherry
# All elementsecho ${fruits[@]} # apple banana cherry dateecho ${fruits[*]} # apple banana cherry date
# Number of elementsecho ${#fruits[@]} # 4
# Length of specific elementecho ${#fruits[0]} # 5 (length of "apple")Array Operations
Section titled “Array Operations”fruits=("apple" "banana" "cherry")
# Add elementfruits+=("date")# orfruits[4]="elderberry"
# Remove elementunset fruits[1] # Remove second elementfruits=("${fruits[@]}") # Reindex
# Slice arrayecho ${fruits[@]:1:2} # banana cherry (from index 1, length 2)
# Search in arrayfruits=("apple" "banana" "cherry")echo "${fruits[@]}" | grep -q "banana" && echo "Found"
# Loop through arrayfor fruit in "${fruits[@]}"; do echo "Fruit: $fruit"doneAssociative Arrays
Section titled “Associative Arrays”Declaring Associative Arrays
Section titled “Declaring Associative Arrays”# Must declare before use (bash 4+)declare -A user_info
# Assign valuesuser_info["name"]="John Doe"user_info["email"]="john@example.com"user_info["role"]="admin"user_info["active"]="true"
# Or initialize all at oncedeclare -A config=( [host]="localhost" [port]="8080" [database]="mydb" [debug]="true")Accessing Associative Arrays
Section titled “Accessing Associative Arrays”declare -A user_infouser_info["name"]="John"user_info["email"]="john@example.com"
# Single elementecho ${user_info["name"]} # John
# All keysecho ${!user_info[@]} # name email
# All valuesecho ${user_info[@]} # John john@example.com
# Number of elementsecho ${#user_info[@]} # 2
# Loop through keys and valuesfor key in "${!user_info[@]}"; do echo "$key = ${user_info[$key]}"donePractical Example: Configuration
Section titled “Practical Example: Configuration”#!/usr/bin/env bashdeclare -A CONFIG
# Database configurationCONFIG[db_host]="localhost"CONFIG[db_port]="5432"CONFIG[db_name]="production"CONFIG[db_user]="appuser"
# Application configurationCONFIG[app_port]="8080"CONFIG[log_level]="info"CONFIG[max_connections]="100"
# Function to get config valueget_config() { local key="$1" echo "${CONFIG[$key]:-}"}
# Usageecho "Database: ${CONFIG[db_host]}:${CONFIG[db_port]}/${CONFIG[db_name]}"echo "Log level: $(get_config log_level)"Best Practices
Section titled “Best Practices”Variable Naming Conventions
Section titled “Variable Naming Conventions”# Use descriptive names# Bad:x=10y="hello"fn()
# Good:max_retries=10user_greeting="Hello"create_backup()
# Use UPPERCASE for constantsreadonly MAX_CONNECTIONS=100readonly DEFAULT_TIMEOUT=30Quoting Variables
Section titled “Quoting Variables”# Always quote variable expansions to handle spacesname="John Smith"echo "$name" # John Smithecho $name # John Smith (might work but unsafe)
# Quotes prevent word splitting and glob expansionfile="my document.txt"ls "$file" # Worksls $file # Might fail - tries to find "my" and "document.txt"
# Use double quotes for variable expansion# Use single quotes for literal stringsUsing readonly
Section titled “Using readonly”# Constants should be readonlyreadonly APP_NAME="MyApp"readonly LOG_DIR="/var/log/myapp"readonly CONFIG_FILE="/etc/myapp/config.yaml"
# Try to modify - will get error# APP_NAME="NewName" # Error: readonly variableCommon Mistakes
Section titled “Common Mistakes”Mistake 1: Spaces Around =
Section titled “Mistake 1: Spaces Around =”# Wrong - causes errorvar = "value"# Error: var: command not found
# Correctvar="value"Mistake 2: Not Quoting Variables
Section titled “Mistake 2: Not Quoting Variables”# Wrongfilename="my file.txt"rm $filename
# Correct - always quoterm "$filename"Mistake 3: Unset Variables
Section titled “Mistake 3: Unset Variables”# Wrong - with set -u, this causes errorecho $undefined_var
# Correct - provide defaultecho "${undefined_var:-default}"
# Or check if setif [ -z "${var+x}" ]; then echo "Variable is not set"fiMistake 4: Array Assignment
Section titled “Mistake 4: Array Assignment”# Wrong - assigns to first elementarray=value# This creates array with one element
# Correct - use parenthesesarray=(value1 value2 value3)Practical Examples
Section titled “Practical Examples”Example 1: Configuration Loader
Section titled “Example 1: Configuration Loader”#!/usr/bin/env bash# Default configurationdeclare -A CONFIGCONFIG[host]="localhost"CONFIG[port]="8080"CONFIG[debug]="false"
# Load from file (key=value format)load_config() { local config_file="$1"
if [ -f "$config_file" ]; then while IFS='=' read -r key value; do # Skip comments and empty lines [[ "$key" =~ ^# ]] && continue [[ -z "$key" ]] && continue
CONFIG["$key"]="$value" done < "$config_file" fi}
# Usageload_config "/etc/myapp/config.env"
# Access configecho "Host: ${CONFIG[host]}"echo "Port: ${CONFIG[port]}"Example 2: Environment-Based Settings
Section titled “Example 2: Environment-Based Settings”#!/usr/bin/env bash# Environment-based configurationENVIRONMENT="${ENVIRONMENT:-development}"
case "$ENVIRONMENT" in production) readonly DB_HOST="prod-db.example.com" readonly DB_PORT="5432" readonly LOG_LEVEL="warn" readonly MAX_CONNECTIONS="200" ;; staging) readonly DB_HOST="staging-db.example.com" readonly DB_PORT="5432" readonly LOG_LEVEL="debug" readonly MAX_CONNECTIONS="50" ;; development|*) readonly DB_HOST="localhost" readonly DB_PORT="5432" readonly LOG_LEVEL="debug" readonly MAX_CONNECTIONS="10" ;;esac
echo "Environment: $ENVIRONMENT"echo "Database: $DB_HOST:$DB_PORT"echo "Log Level: $LOG_LEVEL"Example 3: Argument Parser
Section titled “Example 3: Argument Parser”#!/usr/bin/env bash# Default valuesVERBOSE=falseFORCE=falseOUTPUT_FILE=""
# Parse argumentswhile [ $# -gt 0 ]; do case "$1" in -v|--verbose) VERBOSE=true shift ;; -f|--force) FORCE=true shift ;; -o|--output) OUTPUT_FILE="$2" shift 2 ;; -h|--help) echo "Usage: $0 [-v|--verbose] [-f|--force] [-o|--output FILE]" exit 0 ;; *) echo "Unknown option: $1" exit 1 ;; esacdone
# Use parsed values[ "$VERBOSE" = true ] && echo "Verbose mode enabled"[ "$FORCE" = true ] && echo "Force mode enabled"[ -n "$OUTPUT_FILE" ] && echo "Output file: $OUTPUT_FILE"Summary
Section titled “Summary”In this chapter, you learned:
- ✅ Variable declaration and assignment
- ✅ Accessing variables with $ and ${}
- ✅ Parameter expansion for default values
- ✅ Special parameters ($0, $1, $@, $*, $?, $$, etc.)
- ✅ Environment variables and their scope
- ✅ Indexed arrays and associative arrays
- ✅ Best practices for variable naming
- ✅ Common mistakes and how to avoid them
- ✅ Practical examples for DevOps scripts
Exercises
Section titled “Exercises”Level 1: Basics
Section titled “Level 1: Basics”- Create a script that declares and uses several variables
- Practice using parameter expansion with defaults
- Create an array and iterate through its elements
Level 2: Intermediate
Section titled “Level 2: Intermediate”- Write a configuration loader that reads key=value pairs
- Implement an argument parser with multiple options
- Create an associative array for storing server information
Level 3: Advanced
Section titled “Level 3: Advanced”- Write a function that modifies a global variable using nameref
- Create a script that handles environment-specific configurations
- Implement a simple key-value store using associative arrays
Next Steps
Section titled “Next Steps”Now that you understand variables, continue to learn about special variables and operators in the next chapters.
Previous Chapter: Basic Syntax Next Chapter: Special Variables