Skip to content

Special_variables

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 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 │ │
│ └──────────────────┘ └──────────────────┘ └───────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────┘

script_name.sh
#!/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 located
script_dir=$(cd "$(dirname "$0")" && pwd)
echo "Script directory: $script_dir"
arguments.sh
#!/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 beyond
echo "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 1
fi
echo "Deploying version $VERSION to $ENVIRONMENT"
arg_count.sh
#!/usr/bin/env bash
# $# contains the number of positional parameters
echo "Number of arguments: $#"
# Validate minimum arguments
if [ $# -lt 3 ]; then
echo "Error: At least 3 arguments required"
echo "Usage: $0 <arg1> <arg2> <arg3> [arg4] [arg5]"
exit 1
fi
echo "First three arguments:"
echo " 1: $1"
echo " 2: $2"
echo " 3: $3"
if [ $# -gt 3 ]; then
echo "Additional arguments: ${@:4}"
fi
all_args.sh
#!/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.sh
#!/usr/bin/env bash
# $? contains the exit status of the most recently executed foreground pipeline
# Successful command (exit 0)
true
echo "Exit status of 'true': $?" # Output: 0
# Failed command (exit 1)
false
echo "Exit status of 'false': $?" # Output: 1
# Command with error
ls /nonexistent_directory
echo "Exit status of failed ls: $?" # Output: 2
# Practical use in conditionals
if grep -q "pattern" file.txt; then
echo "Pattern found!"
else
echo "Pattern not found (exit status: $?)"
fi
# Store exit status for later use
command_that_might_fail
EXIT_STATUS=$?
if [ $EXIT_STATUS -ne 0 ]; then
echo "Command failed with status: $EXIT_STATUS"
fi
┌─────────────────────────────────────────────────────────────────────┐
│ 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_arg.sh
#!/usr/bin/env bash
# $_ holds the last argument of the previous command
# Example 1
echo "hello"
echo "Last argument was: $_" # Output: hello
# Example 2
mkdir -p /tmp/new_directory
cd "$_"
echo "Changed to: $_" # Output: /tmp/new_directory
# Example 3
cp file1.txt file2.txt
echo "Copied to: $_" # Output: file2.txt

process_id.sh
#!/usr/bin/env bash
# $$ contains the process ID of the current shell
echo "Current shell PID: $$"
# Common use: Creating unique temporary files
TEMP_FILE="/tmp/myapp_$$.tmp"
echo "Temp file: $TEMP_FILE"
# Useful for:
# - Lock files
# - Unique log files
# - Debugging (matching processes)
# Example: Lock file for script
LOCK_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
fi
fi
# Write our PID to lock file
echo $$ > "$LOCK_FILE"
trap 'rm -f "$LOCK_FILE"' EXIT
echo "Script running with PID: $$"
background_pid.sh
#!/usr/bin/env bash
# $! contains the PID of the most recently started background job
# Start a background process
sleep 30 &
echo "Background job PID: $!"
# Capture it for later use
BACKGROUND_PID=$!
echo "Stored PID: $BACKGROUND_PID"
# Wait for the background process
wait $BACKGROUND_PID 2>/dev/null
EXIT_CODE=$?
echo "Background job exited with code: $EXIT_CODE"
# Practical example: Start multiple background workers
start_workers() {
for i in {1..3}; do
worker.sh "$i" &
WORKER_PIDS+=($!)
done
echo "Started workers: ${WORKER_PIDS[*]}"
}
start_workers
# Wait for all workers
for pid in "${WORKER_PIDS[@]}"; do
wait $pid
echo "Worker $pid completed"
done
parent_pid.sh
#!/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 name
parent_name=$(ps -p $PPID -o comm=)
echo "Parent process: $parent_name"
# Example: Only run from specific parent
if [ "$(ps -p $PPID -o comm=)" != "cron" ]; then
echo "Warning: This script should be run from cron"
fi

shell_options.sh
#!/usr/bin/env bash
# $- contains the current set of shell options
echo "Shell options: $-"
# Check specific options
if [[ $- == *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 mode

bash_version.sh
#!/usr/bin/env bash
# $BASH_VERSION contains the version of bash
echo "Bash version: $BASH_VERSION"
# Useful for feature detection
if [[ "${BASH_VERSION}" < "4.0" ]]; then
echo "Warning: Some features require Bash 4.0+"
fi
# Check specific version
version_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"
fi
fi
source_file.sh
#!/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
Terminal window
# 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.sh
#!/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 timing
echo "Starting operation..."
START=$SECONDS
# Do something
sleep 2
ELAPSED=$((SECONDS - START))
echo "Operation took: $ELAPSED seconds"
# Simple progress indicator
for i in {1..5}; do
echo -n "."
sleep 1
done
echo " Done! (${SECONDS}s)"

funcname.sh
#!/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] main

lineno.sh
#!/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 messages
validate_input() {
if [ -z "$1" ]; then
echo "Error: Empty input at line $LINENO"
return 1
fi
}
bash_lineno.sh
#!/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 15
level1
# Output:
# Called from line: 18
# Called from line: 15
# Full call stack line numbers: 18 15

random.sh
#!/usr/bin/env bash
# $RANDOM generates a random integer between 0 and 32767
# Basic usage
echo "Random number: $RANDOM"
# Generate number in range (1-100)
RANGE=100
number=$((RANDOM % RANGE + 1))
echo "Random number (1-100): $number"
# Generate random string
random_string=$(head -c 32 /dev/urandom | base64)
echo "Random string: $random_string"
# Generate random hex string
random_hex=$(printf '%x\n' $RANDOM$RANDOM)
echo "Random hex: $random_hex"
# For cryptographic randomness, use /dev/urandom or /dev/random
# For DevOps: generate unique identifiers
uuid=$(cat /proc/sys/kernel/random/uuid)
echo "UUID: $uuid"

Example 1: Deployment Script with Full Argument Handling

Section titled “Example 1: Deployment Script with Full Argument Handling”
deploy.sh
#!/usr/bin/env bash
set -euo pipefail
# Script metadata
readonly SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_VERSION="1.0.0"
# Default values
ENVIRONMENT="staging"
VERSION="latest"
DRY_RUN=false
FORCE=false
# Function to display usage
usage() {
cat << EOF
Usage: $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 arguments
while [ $# -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
;;
esac
done
# Validate arguments
if [ -z "$ENVIRONMENT" ]; then
echo "Error: Environment is required"
usage
exit 2
fi
if [[ ! "$ENVIRONMENT" =~ ^(staging|production)$ ]]; then
echo "Error: Invalid environment. Must be 'staging' or 'production'"
exit 2
fi
# Main deployment logic
echo "=========================================="
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 0
fi
echo "Starting deployment..."
error_handling.sh
#!/usr/bin/env bash
set -euo pipefail
# Error handler function
error_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 trap
trap 'error_handler $LINENO' ERR
# Function that might fail
risky_operation() {
echo "Performing risky operation..."
false # This will trigger error handler
}
# This will trigger the error trap
risky_operation
monitor.sh
#!/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 function
log() {
local level="$1"
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE"
}
# Check if already running
check_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 logic
main() {
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
}
main

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

  1. Create a script that prints all its arguments
  2. Write a script that validates the number of arguments
  3. Create a script that uses $RANDOM to generate a password
  1. Implement argument parsing with support for flags and options
  2. Write a script that logs errors with line numbers
  3. Create a script that prevents multiple instances from running
  1. Implement a deployment script with full argument handling
  2. Write a function that traces the call stack
  3. Create a comprehensive monitoring script using multiple special variables

Continue to the next chapter to learn about operators in bash.


Previous Chapter: Variables and Parameters Next Chapter: Operators