Special_variables
Chapter 5: Special Variables
Section titled “Chapter 5: Special Variables”Overview
Section titled “Overview”This chapter provides an in-depth exploration of special variables in Bash. These are pre-defined variables that contain important information about the shell environment, script execution, and command-line arguments. Understanding these variables is crucial for writing robust DevOps scripts.
Special Variable Categories
Section titled “Special Variable Categories”┌─────────────────────────────────────────────────────────────────────┐│ SPECIAL VARIABLES OVERVIEW │└─────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐│ ││ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ ││ │ Positional │ │ Status │ │ Process │ ││ │ Parameters │ │ Variables │ │ IDs │ ││ │ │ │ │ │ │ ││ │ $0, $1-$9, ${10}│ │ $?, $_, $- │ │ $$, $!, $PPID│ ││ │ $#, $@, $* │ │ │ │ │ ││ └──────────────────┘ └──────────────────┘ └───────────────┘ ││ ││ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ ││ │ Shell │ │ Function │ │ Debugging │ ││ │ Variables │ │ Context │ │ Variables │ ││ │ │ │ │ │ │ ││ │ $BASH_VERSION │ │ $FUNCNAME │ │ $LINENO │ ││ │ $BASH_SOURCE │ │ $FUNCNAME[@] │ │ $BASH_LINENO │ ││ │ $SECONDS │ │ $BASH_SUBSHELL │ │ $BASH_SOURCE │ ││ └──────────────────┘ └──────────────────┘ └───────────────┘ ││ │└───────────────────────────────────────────────────────────────────────┘Positional Parameters
Section titled “Positional Parameters”$0 - Script Name
Section titled “$0 - Script Name”#!/usr/bin/env bash# $0 contains the name of the script as it was invoked
echo "Script name: $0"
# When run as: ./script_name.sh# Output: Script name: ./script_name.sh
# When run as: /full/path/script_name.sh# Output: Script name: /full/path/script_name.sh
# When run as: bash script_name.sh# Output: Script name: script_name.sh
# Extract just the script name (without path)script_name=$(basename "$0")echo "Script name (basename): $script_name"
# Get the directory where the script is locatedscript_dir=$(cd "$(dirname "$0")" && pwd)echo "Script directory: $script_dir"$1 through ${N} - Arguments
Section titled “$1 through ${N} - Arguments”#!/usr/bin/env bash# $1 is the first argument# $2 is the second argument# etc.
# Arguments beyond 9 require braces: ${10}, ${11}, etc.
echo "First argument: $1"echo "Second argument: $2"echo "Third argument: $3"
# Access arguments 10 and beyondecho "10th argument: ${10}"echo "11th argument: ${11}"
# Practical example: Using arguments for a deployment script# Usage: ./deploy.sh <environment> <version>ENVIRONMENT="$1"VERSION="$2"
if [ -z "$ENVIRONMENT" ] || [ -z "$VERSION" ]; then echo "Usage: $0 <environment> <version>" echo "Example: $0 production v1.2.3" exit 1fi
echo "Deploying version $VERSION to $ENVIRONMENT"$# - Argument Count
Section titled “$# - Argument Count”#!/usr/bin/env bash# $# contains the number of positional parameters
echo "Number of arguments: $#"
# Validate minimum argumentsif [ $# -lt 3 ]; then echo "Error: At least 3 arguments required" echo "Usage: $0 <arg1> <arg2> <arg3> [arg4] [arg5]" exit 1fi
echo "First three arguments:"echo " 1: $1"echo " 2: $2"echo " 3: $3"
if [ $# -gt 3 ]; then echo "Additional arguments: ${@:4}"fi$@ and $* - All Arguments
Section titled “$@ and $* - All Arguments”#!/usr/bin/env bash# Both $@ and $* expand to all positional parameters
echo "Using \$@:"for arg in "$@"; do echo " Argument: '$arg'"done
echo ""echo "Using \$*:"for arg in "$*"; do echo " Argument: '$arg'"done
# The difference becomes clear with "quoted" expansion:# "$@" - Each argument stays separate (recommended)# "$*" - All arguments combined into one string
# Example with arguments containing spaces:# Run: ./script.sh "first arg" "second arg" "third"
echo ""echo "Quoted \$@ preserves separation:"printf " [%s]\n" "$@"
echo ""echo "Quoted \$* combines all:"printf " [%s]\n" "$*"Exit Status Variables
Section titled “Exit Status Variables”$? - Last Command Exit Status
Section titled “$? - Last Command Exit Status”#!/usr/bin/env bash# $? contains the exit status of the most recently executed foreground pipeline
# Successful command (exit 0)trueecho "Exit status of 'true': $?" # Output: 0
# Failed command (exit 1)falseecho "Exit status of 'false': $?" # Output: 1
# Command with errorls /nonexistent_directoryecho "Exit status of failed ls: $?" # Output: 2
# Practical use in conditionalsif grep -q "pattern" file.txt; then echo "Pattern found!"else echo "Pattern not found (exit status: $?)"fi
# Store exit status for later usecommand_that_might_failEXIT_STATUS=$?if [ $EXIT_STATUS -ne 0 ]; then echo "Command failed with status: $EXIT_STATUS"fiCommon Exit Codes
Section titled “Common Exit Codes”┌─────────────────────────────────────────────────────────────────────┐│ COMMON EXIT CODES │└─────────────────────────────────────────────────────────────────────┘
┌──────────┬─────────────────────────────────────────────────────────┐│ Code │ Description │├──────────┼─────────────────────────────────────────────────────────┤│ 0 │ Success ││ 1 │ General error ││ 2 │ Misuse of shell command ││ 126 │ Command not executable ││ 127 │ Command not found ││ 128 │ Invalid exit argument ││ 130 │ Script terminated by Ctrl+C (SIGINT) ││ 255 │ Exit status out of range │└──────────┴─────────────────────────────────────────────────────────┘
# Custom exit codes for your scripts:# 1 - General error# 2 - Invalid arguments# 3 - File not found# 4 - Permission denied# 5 - Configuration error# 10 - Network error# 20 - Database error$_ - Last Argument
Section titled “$_ - Last Argument”#!/usr/bin/env bash# $_ holds the last argument of the previous command
# Example 1echo "hello"echo "Last argument was: $_" # Output: hello
# Example 2mkdir -p /tmp/new_directorycd "$_"echo "Changed to: $_" # Output: /tmp/new_directory
# Example 3cp file1.txt file2.txtecho "Copied to: $_" # Output: file2.txtProcess ID Variables
Section titled “Process ID Variables”$$ - Current Shell PID
Section titled “$$ - Current Shell PID”#!/usr/bin/env bash# $$ contains the process ID of the current shell
echo "Current shell PID: $$"
# Common use: Creating unique temporary filesTEMP_FILE="/tmp/myapp_$$.tmp"echo "Temp file: $TEMP_FILE"
# Useful for:# - Lock files# - Unique log files# - Debugging (matching processes)
# Example: Lock file for scriptLOCK_FILE="/var/run/myscript.lock"if [ -f "$LOCK_FILE" ]; then PID=$(cat "$LOCK_FILE") if kill -0 "$PID" 2>/dev/null; then echo "Script already running (PID: $PID)" exit 1 fifi
# Write our PID to lock fileecho $$ > "$LOCK_FILE"trap 'rm -f "$LOCK_FILE"' EXIT
echo "Script running with PID: $$"$! - Last Background Job PID
Section titled “$! - Last Background Job PID”#!/usr/bin/env bash# $! contains the PID of the most recently started background job
# Start a background processsleep 30 &echo "Background job PID: $!"
# Capture it for later useBACKGROUND_PID=$!echo "Stored PID: $BACKGROUND_PID"
# Wait for the background processwait $BACKGROUND_PID 2>/dev/nullEXIT_CODE=$?echo "Background job exited with code: $EXIT_CODE"
# Practical example: Start multiple background workersstart_workers() { for i in {1..3}; do worker.sh "$i" & WORKER_PIDS+=($!) done echo "Started workers: ${WORKER_PIDS[*]}"}
start_workers
# Wait for all workersfor pid in "${WORKER_PIDS[@]}"; do wait $pid echo "Worker $pid completed"done$PPID - Parent Process ID
Section titled “$PPID - Parent Process ID”#!/usr/bin/env bash# $PPID contains the process ID of the parent shell
echo "Parent shell PID: $PPID"
# Useful for:# - Checking if script was started by a specific parent# - Process tree analysis
# Check parent process nameparent_name=$(ps -p $PPID -o comm=)echo "Parent process: $parent_name"
# Example: Only run from specific parentif [ "$(ps -p $PPID -o comm=)" != "cron" ]; then echo "Warning: This script should be run from cron"fiShell Options Variable
Section titled “Shell Options Variable”$- - Current Options
Section titled “$- - Current Options”#!/usr/bin/env bash# $- contains the current set of shell options
echo "Shell options: $-"
# Check specific optionsif [[ $- == *e* ]]; then echo "Exit on error (-e) is enabled"fi
if [[ $- == *u* ]]; then echo "Exit on unset variable (-u) is enabled"fi
if [[ $- == *x* ]]; then echo "Debug mode (-x) is enabled"fi
# Common options:# e - Exit immediately if command exits with non-zero status# u - Treat unset variables as errors# x - Print commands before execution (trace)# v - Print input lines as they're read# o pipefail - Pipeline fails if any command fails# h - Remember location of commands# i - Interactive modeShell Version Variables
Section titled “Shell Version Variables”$BASH_VERSION
Section titled “$BASH_VERSION”#!/usr/bin/env bash# $BASH_VERSION contains the version of bash
echo "Bash version: $BASH_VERSION"
# Useful for feature detectionif [[ "${BASH_VERSION}" < "4.0" ]]; then echo "Warning: Some features require Bash 4.0+"fi
# Check specific versionversion_info=(${BASH_VERSION//./ })major=${version_info[0]}minor=${version_info[1]}patch=${version_info[2]}
echo "Major: $major, Minor: $minor, Patch: $patch"
# Example: Use associative arrays only if bash 4+if [[ ${BASH_VERSION} =~ ^([0-9]+)\. ]]; then if [[ ${BASH_VERSION%%.*} -ge 4 ]]; then echo "Bash 4+ detected, can use associative arrays" fifi$BASH_SOURCE
Section titled “$BASH_SOURCE”#!/usr/bin/env bash# $BASH_SOURCE contains the source file name
# In a sourced file, this gives the path to the sourced file# Useful for locating files relative to the script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"echo "Script directory: $SCRIPT_DIR"
# When sourcing this file from another script:# source ./source_file.sh# Will show: /full/path/to/source_file.sh$BASH_EXECUTION_STRING
Section titled “$BASH_EXECUTION_STRING”# This is set when bash executes commands with -c option
bash -c 'echo "Execution string: $BASH_EXECUTION_STRING"'# Output: Execution string: echo "Execution string: $BASH_EXECUTION_STRING"Timing Variables
Section titled “Timing Variables”$SECONDS
Section titled “$SECONDS”#!/usr/bin/env bash# $SECONDS returns the number of seconds since the shell was started
echo "Shell has been running for: $SECONDS seconds"
# Can be used for simple timingecho "Starting operation..."START=$SECONDS
# Do somethingsleep 2
ELAPSED=$((SECONDS - START))echo "Operation took: $ELAPSED seconds"
# Simple progress indicatorfor i in {1..5}; do echo -n "." sleep 1doneecho " Done! (${SECONDS}s)"Function Context Variables
Section titled “Function Context Variables”$FUNCNAME
Section titled “$FUNCNAME”#!/usr/bin/env bash# $FUNCNAME is an array containing the current function call stack
function main() { echo "Current function: ${FUNCNAME[0]}" echo "Called by: ${FUNCNAME[1]:-main script}" call_deep}
function call_deep() { echo "Current function: ${FUNCNAME[0]}" echo "Call stack:" for i in "${!FUNCNAME[@]}"; do echo " [$i] ${FUNCNAME[$i]}" done}
main
# Output:# Current function: main# Called by: main script# Current function: call_deep# Call stack:# [0] call_deep# [1] mainDebugging Variables
Section titled “Debugging Variables”$LINENO
Section titled “$LINENO”#!/usr/bin/env bash# $LINENO returns the current line number in the script
echo "This is line $LINENO"
error_handler() { echo "Error occurred at line $LINENO"}
# Useful for error messagesvalidate_input() { if [ -z "$1" ]; then echo "Error: Empty input at line $LINENO" return 1 fi}$BASH_LINENO
Section titled “$BASH_LINENO”#!/usr/bin/env bash# $BASH_LINENO is an array of line numbers in the call stack
function level1() { echo "Called from line: ${BASH_LINENO[0]}" level2}
function level2() { echo "Called from line: ${BASH_LINENO[0]}" echo "Full call stack line numbers: ${BASH_LINENO[@]}"}
# Call from line 15level1
# Output:# Called from line: 18# Called from line: 15# Full call stack line numbers: 18 15Random Number
Section titled “Random Number”$RANDOM
Section titled “$RANDOM”#!/usr/bin/env bash# $RANDOM generates a random integer between 0 and 32767
# Basic usageecho "Random number: $RANDOM"
# Generate number in range (1-100)RANGE=100number=$((RANDOM % RANGE + 1))echo "Random number (1-100): $number"
# Generate random stringrandom_string=$(head -c 32 /dev/urandom | base64)echo "Random string: $random_string"
# Generate random hex stringrandom_hex=$(printf '%x\n' $RANDOM$RANDOM)echo "Random hex: $random_hex"
# For cryptographic randomness, use /dev/urandom or /dev/random# For DevOps: generate unique identifiersuuid=$(cat /proc/sys/kernel/random/uuid)echo "UUID: $uuid"Comprehensive Examples
Section titled “Comprehensive Examples”Example 1: Deployment Script with Full Argument Handling
Section titled “Example 1: Deployment Script with Full Argument Handling”#!/usr/bin/env bashset -euo pipefail
# Script metadatareadonly SCRIPT_NAME="$(basename "$0")"readonly SCRIPT_VERSION="1.0.0"
# Default valuesENVIRONMENT="staging"VERSION="latest"DRY_RUN=falseFORCE=false
# Function to display usageusage() { cat << EOFUsage: $SCRIPT_NAME [OPTIONS] <environment> [version]
Deploy application to specified environment.
Arguments: environment Target environment (staging, production) version Application version (default: latest)
Options: -h, --help Show this help message -v, --version Show version -d, --dry-run Perform a dry run -f, --force Force deployment
Examples: $SCRIPT_NAME production v1.2.3 $SCRIPT_NAME --dry-run staging $SCRIPT_NAME -f production
EOF}
# Parse command line argumentswhile [ $# -gt 0 ]; do case "$1" in -h|--help) usage exit 0 ;; -v|--version) echo "$SCRIPT_NAME version $SCRIPT_VERSION" exit 0 ;; -d|--dry-run) DRY_RUN=true shift ;; -f|--force) FORCE=true shift ;; -*) echo "Unknown option: $1" usage exit 2 ;; *) if [ -z "$ENVIRONMENT" ]; then ENVIRONMENT="$1" elif [ "$VERSION" = "latest" ]; then VERSION="$1" fi shift ;; esacdone
# Validate argumentsif [ -z "$ENVIRONMENT" ]; then echo "Error: Environment is required" usage exit 2fi
if [[ ! "$ENVIRONMENT" =~ ^(staging|production)$ ]]; then echo "Error: Invalid environment. Must be 'staging' or 'production'" exit 2fi
# Main deployment logicecho "=========================================="echo " Deployment Script"echo "=========================================="echo "Environment: $ENVIRONMENT"echo "Version: $VERSION"echo "Dry Run: $DRY_RUN"echo "Force: $FORCE"echo "Script: $SCRIPT_NAME"echo "PID: $$"echo "=========================================="
if [ "$DRY_RUN" = true ]; then echo "[DRY RUN] Would deploy $VERSION to $ENVIRONMENT" exit 0fi
echo "Starting deployment..."Example 2: Comprehensive Error Handling
Section titled “Example 2: Comprehensive Error Handling”#!/usr/bin/env bashset -euo pipefail
# Error handler functionerror_handler() { local exit_code=$? local line_number=$1
echo "=========================================" echo "ERROR: Command failed at line $line_number" echo "Exit code: $exit_code" echo "Command: ${BASH_COMMAND}" echo "========================================="
# Log to file echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR at line $line_number" >> error.log
exit $exit_code}
# Set error traptrap 'error_handler $LINENO' ERR
# Function that might failrisky_operation() { echo "Performing risky operation..." false # This will trigger error handler}
# This will trigger the error traprisky_operationExample 3: Process Monitoring Script
Section titled “Example 3: Process Monitoring Script”#!/usr/bin/env bash# Monitor script using special variables
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"LOG_FILE="/var/log/${SCRIPT_NAME%.sh}.log"
# Log functionlog() { local level="$1" shift echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE"}
# Check if already runningcheck_running() { local script_pid=$$
# Find other instances local pids=$(pgrep -f "$SCRIPT_NAME" 2>/dev/null || true)
for pid in $pids; do if [ "$pid" -ne "$script_pid" ]; then log "WARN" "Another instance is running (PID: $pid)" return 1 fi done
log "INFO" "Starting with PID: $script_pid" return 0}
# Main monitoring logicmain() { log "INFO" "Monitor script started" log "INFO" "Bash version: $BASH_VERSION" log "INFO" "Shell options: $-"
check_running || exit 1
# Monitor loop while true; do # Your monitoring logic here sleep 60 done}
mainSummary
Section titled “Summary”In this chapter, you learned about:
- ✅ Positional parameters ($0, $1-$9, ${N}, $#, $@, $*)
- ✅ Exit status variables ($?, $_)
- ✅ Process ID variables ($$, $!, $PPID)
- ✅ Shell options variable ($-)
- ✅ Shell version variables ($BASH_VERSION, $BASH_SOURCE)
- ✅ Timing variables ($SECONDS)
- ✅ Function context variables ($FUNCNAME)
- ✅ Debugging variables ($LINENO, $BASH_LINENO)
- ✅ Random number generation ($RANDOM)
- ✅ Practical examples for DevOps
Exercises
Section titled “Exercises”Level 1: Basics
Section titled “Level 1: Basics”- Create a script that prints all its arguments
- Write a script that validates the number of arguments
- Create a script that uses $RANDOM to generate a password
Level 2: Intermediate
Section titled “Level 2: Intermediate”- Implement argument parsing with support for flags and options
- Write a script that logs errors with line numbers
- Create a script that prevents multiple instances from running
Level 3: Advanced
Section titled “Level 3: Advanced”- Implement a deployment script with full argument handling
- Write a function that traces the call stack
- Create a comprehensive monitoring script using multiple special variables
Next Steps
Section titled “Next Steps”Continue to the next chapter to learn about operators in bash.
Previous Chapter: Variables and Parameters Next Chapter: Operators