Bash Special Variables
Understanding built-in shell variables
💎 What are Special Variables?
Bash special variables are predefined variables that hold important information about the shell environment, script execution, and command results. They provide access to arguments, exit codes, process IDs, and other system information.
# Display script name
echo $0
# Show exit status of last command
echo $?
Output:
/bin/bash 0
Common Special Variables
$0
Script or shell name
echo $0
$#
Number of arguments
echo $#
$?
Exit status of last command
echo $?
$$
Current process ID
echo $$
🔹 Positional Parameters
Positional parameters ($1, $2, etc.) provide access to command-line arguments passed to your script. They allow scripts to accept dynamic input, making them flexible and reusable. $0 holds the script's name, while $1 is the first argument, $2 the second, and so on. These are the primary mechanism for passing filenames, options, or configuration data into a script, forming the basis for user interaction and script configurability.
#!/bin/bash
# Save as script.sh and run: ./script.sh arg1 arg2 arg3
# Script name
echo "Script name: $0"
# First argument
echo "First argument: $1"
# Second argument
echo "Second argument: $2"
# All arguments as separate words
echo "All arguments: $@"
# All arguments as single string
echo "All arguments: $*"
# Number of arguments
echo "Argument count: $#"
Output:
Script name: ./script.sh First argument: arg1 Second argument: arg2 All arguments: arg1 arg2 arg3 Argument count: 3
🔹 Exit Status Variable
The $? variable is a critical tool for error checking and flow control, storing the exit code of the last executed command. A value of 0 indicates success, while any non-zero value (1-255) signals an error. Scripts should check this variable after important commands to decide their next action—whether to proceed, retry, or abort with an error message. Proper use of $? is fundamental to creating robust, reliable automation that can handle unexpected failures gracefully.
#!/bin/bash
# Successful command
ls /home
echo "Exit status: $?"
# Failed command
ls /nonexistent
echo "Exit status: $?"
# Using in conditional
if [ $? -eq 0 ]; then
echo "Command succeeded"
else
echo "Command failed"
fi
# Custom exit status
exit 5
Output:
user1 user2 Exit status: 0 ls: cannot access '/nonexistent': No such file or directory Exit status: 2 Command failed
🔹 Process ID Variables
Process ID variables give scripts insight into their own execution environment and running processes. $$ contains the PID of the current shell, while $! holds the PID of the most recent background command. These are useful for creating unique temporary filenames (e.g., /tmp/myscript.$$), managing background jobs, sending signals to specific processes, and implementing simple process coordination or locking mechanisms within scripts.
#!/bin/bash
# Current shell process ID
echo "Current PID: $$"
# Create unique temp file
TEMP_FILE="/tmp/script_$$.tmp"
echo "Temp file: $TEMP_FILE"
# Start background process
sleep 100 &
# Last background process ID
echo "Background PID: $!"
# Wait for background process
wait $!
echo "Background process finished"
Output:
Current PID: 12345 Temp file: /tmp/script_12345.tmp Background PID: 12346 Background process finished
🔹 Shell Option Variables
Special variables reveal the state and configuration of the Bash shell itself. $SHELLOPTS lists enabled shell options (like errexit or nounset). $BASH_VERSINFO is an array with version details. $SHLVL indicates nesting depth (incremented in subshells). Checking these helps write portable scripts that adapt their behavior based on the shell's capabilities or settings, ensuring compatibility across different environments.
#!/bin/bash
# Shell options
echo "Shell options: $-"
# Shell level (nesting depth)
echo "Shell level: $SHLVL"
# Bash version
echo "Bash version: $BASH_VERSION"
# Check if interactive shell
if [[ $- == *i* ]]; then
echo "Interactive shell"
else
echo "Non-interactive shell"
fi
# Current shell
echo "Current shell: $SHELL"
Output:
Shell options: himBH Shell level: 1 Bash version: 5.1.16 Interactive shell Current shell: /bin/bash
🔹 User and System Variables
Predefined variables provide immediate access to key user and system information. $USER and $HOME identify the user, while $PWD is the current directory. $HOSTNAME gives the system name. Variables like $PATH, $LANG, and $TERM describe the environment. Using these variables allows scripts to behave contextually—adjusting paths, personalizing output, or making decisions based on the user, location, or system they are running on.
#!/bin/bash
# Current username
echo "User: $USER"
# Home directory
echo "Home: $HOME"
# Current directory
echo "PWD: $PWD"
# Previous directory
echo "Old PWD: $OLDPWD"
# Hostname
echo "Hostname: $HOSTNAME"
# User ID
echo "UID: $UID"
# Random number
echo "Random: $RANDOM"
Output:
User: john Home: /home/john PWD: /home/john/projects Old PWD: /home/john Hostname: mycomputer UID: 1000 Random: 23847
🔹 IFS and Field Separator
The Internal Field Separator (IFS) controls how Bash splits strings into words for commands like read and loops. By default, it contains space, tab, and newline. You can modify it to parse CSV data (IFS=',') or process lines with custom delimiters. It's crucial to save the original IFS value (OLD_IFS=$IFS) and restore it after use to avoid unexpected side effects in other parts of your script.
#!/bin/bash
# Default IFS (space, tab, newline)
echo "Default IFS: '$IFS'"
# Parse CSV data
DATA="apple,banana,orange"
IFS=',' read -ra FRUITS <<< "$DATA"
for fruit in "${FRUITS[@]}"; do
echo "Fruit: $fruit"
done
# Parse PATH variable
IFS=':' read -ra PATHS <<< "$PATH"
echo "First PATH: ${PATHS[0]}"
# Restore IFS
IFS=$' \t\n'
Output:
Default IFS: ' ' Fruit: apple Fruit: banana Fruit: orange First PATH: /usr/local/bin
🔹 Practical Examples
Everyday uses of head include validating data files, monitoring log heads, and creating data samples. A data scientist might run head -1000 large_dataset.csv > sample.csv to create a manageable sample for testing scripts. A developer might use head -20 application.log to check recent activity after a deployment. These scenarios highlight head's role as a gatekeeper, providing a safe, controlled way to interact with the beginning of files and streams, which is often where the most current or defining information resides.
#!/bin/bash
# Script with error handling
set -e # Exit on error
# Logging function
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [PID:$$] $1"
}
# Check arguments
if [ $# -lt 2 ]; then
log "Error: Need at least 2 arguments"
echo "Usage: $0 "
exit 1
fi
log "Script started: $0"
log "Arguments: $@"
log "Argument count: $#"
# Process arguments
for arg in "$@"; do
log "Processing: $arg"
done
log "Script completed successfully"
exit 0
Output:
[2024-01-15 10:30:45] [PID:12345] Script started: ./script.sh [2024-01-15 10:30:45] [PID:12345] Arguments: file1 file2 [2024-01-15 10:30:45] [PID:12345] Argument count: 2 [2024-01-15 10:30:45] [PID:12345] Processing: file1 [2024-01-15 10:30:45] [PID:12345] Processing: file2 [2024-01-15 10:30:45] [PID:12345] Script completed successfully