Table of Contents
- Bash Variables: The Basics
- Bash Functions: Reusable Code Blocks
- Advanced Topics
- Best Practices
- Conclusion
- Reference
Bash Variables: The Basics
What Are Variables?
In Bash, a variable is a named container for data (text, numbers, or paths). Variables let you store values, pass data between parts of a script, and make scripts dynamic. For example, instead of hardcoding a file path like /tmp/logs, you can store it in a variable LOG_DIR="/tmp/logs" and reuse it throughout the script.
Types of Variables
Bash variables are loosely typed (no need to declare data types), but they fall into three main categories:
1. Local Variables
Visible only within the current shell or script. They are not inherited by child processes.
Example:
name="Alice" # Local variable (only in this shell/script)
echo "Hello, $name" # Output: Hello, Alice
2. Environment Variables
Inherited by child processes (e.g., scripts, subshells). Use export to make a local variable an environment variable.
Example:
export PATH="$PATH:/usr/local/bin" # Add a directory to the system PATH (environment variable)
echo $HOME # Built-in environment variable (user's home directory)
3. Positional Parameters
Special variables that hold arguments passed to a script or function. Denoted by $1, $2, …, $n (where n is the argument position).
Example:
If you run ./script.sh apple banana, then:
$1=apple$2=banana
Declaring and Assigning Variables
To declare a variable, use variable_name=value (no spaces around =!). To access its value, prefix the name with $ (e.g., $variable_name).
Key Rules:
- No spaces:
name="Bob"is valid;name = "Bob"is not (Bash interpretsnameas a command). - Quoting: Use quotes to handle values with spaces or special characters:
message="Hello, world!" # Double quotes preserve spaces and expand variables echo $message # Output: Hello, world! path='/home/user/My Documents' # Single quotes prevent variable expansion echo $path # Output: /home/user/My Documents - Case sensitivity:
Nameandnameare distinct variables.
Special Variables
Bash provides built-in special variables for common tasks like handling arguments, exit statuses, and script metadata. Here are the most useful ones:
| Variable | Description | Example |
|---|---|---|
$0 | Name of the script/function | echo "Script name: $0" → Script name: ./script.sh |
$1, $2, ... | Positional parameters (arguments) | ./script.sh a b → $1=a, $2=b |
$# | Number of arguments passed | ./script.sh a b c → $#=3 |
$@ | All arguments as separate strings | for arg in "$@"; do echo $arg; done (loops over each arg) |
$* | All arguments as a single string | echo "$*" → a b c (if args are a, b, c) |
$? | Exit status of the last command | ls non_existent_file; echo $? → 2 (error) |
$$ | Process ID (PID) of the current shell | echo "My PID: $$" |
$! | PID of the last background process | sleep 10 &; echo "Background PID: $!" |
Example: Using Special Variables
#!/bin/bash
echo "Script name: $0"
echo "Number of arguments: $#"
echo "Arguments: $@"
echo "First argument: $1"
Run with ./script.sh apple banana:
Script name: ./script.sh
Number of arguments: 2
Arguments: apple banana
First argument: apple
Variable Scope: Global vs. Local
- Global Variables: Visible everywhere in the script (default for variables declared outside functions).
- Local Variables: Visible only within the function or block where they’re declared (use the
localkeyword).
Example: Global vs. Local
#!/bin/bash
global_var="I'm global" # Global variable
my_function() {
local local_var="I'm local" # Local variable (only in my_function)
global_var="Updated global" # Modifies the global variable
echo "Inside function: $local_var, $global_var"
}
my_function
echo "Outside function: $global_var" # Global var was updated
echo "Outside function: $local_var" # Error: local_var: unbound variable
Output:
Inside function: I'm local, Updated global
Outside function: Updated global
./script.sh: line 9: local_var: unbound variable
Bash Functions: Reusable Code Blocks
Functions let you group commands into named, reusable blocks. They eliminate repetition, improve readability, and make scripts easier to debug.
Function Definition Syntax
Bash supports two syntaxes for defining functions:
1. Standard Syntax
function_name() {
# Commands here
}
2. function Keyword Syntax
function function_name {
# Commands here
}
Both are equivalent, but the first syntax is more portable (works in POSIX shells).
Example: Simple Function
greet() {
echo "Hello, $1!" # $1 is the first argument to the function
}
greet "Alice" # Output: Hello, Alice!
greet "Bob" # Output: Hello, Bob!
Parameters and Arguments
Like scripts, functions accept positional parameters ($1, $2, …) and special variables like $@ (all arguments) and $# (number of arguments).
Example: Function with Parameters
sum() {
local a=$1 # First argument
local b=$2 # Second argument
echo $((a + b)) # Arithmetic expansion: $(( ... ))
}
result=$(sum 5 3) # Capture output with command substitution
echo "5 + 3 = $result" # Output: 5 + 3 = 8
Handling Variable Arguments with $@
Use $@ to loop over all arguments dynamically:
print_args() {
echo "Number of args: $#"
for arg in "$@"; do # "$@" preserves spaces in arguments
echo "- $arg"
done
}
print_args "apple" "banana pie" "cherry"
Output:
Number of args: 3
- apple
- banana pie
- cherry
Return Values and Exit Status
Bash functions don’t return values like other programming languages. Instead, they:
- Output text (via
echo,printf, etc.), which can be captured with$(function_name). - Set an exit status (via
return N), whereNis a number (0 = success, 1-255 = error).
Example: Exit Status with return
is_positive() {
local num=$1
if (( num > 0 )); then
return 0 # Success (true)
else
return 1 # Failure (false)
fi
}
if is_positive 5; then # Check exit status (0 = true)
echo "5 is positive"
else
echo "5 is not positive"
fi
Output: 5 is positive
Example: Capturing Output with Command Substitution
get_greeting() {
local time=$(date +%H) # Hour of the day (00-23)
if (( time < 12 )); then
echo "Good morning"
elif (( time < 18 )); then
echo "Good afternoon"
else
echo "Good evening"
fi
}
greeting=$(get_greeting)
echo "$greeting, Alice!" # Output: Good morning, Alice! (if run at 9 AM)
Function Scope
Variables inside a function are global by default (they modify the script’s global state). To limit a variable to the function, use the local keyword.
Example: Using local to Avoid Global Side Effects
count=0
increment_bad() {
count=$((count + 1)) # Modifies the global count
}
increment_good() {
local count=0 # Local variable (shadows global count)
count=$((count + 1))
echo $count # Output local count
}
increment_bad
echo "Global count after bad: $count" # Output: 1
result=$(increment_good)
echo "Global count after good: $count" # Output: 1 (unchanged)
echo "Local result: $result" # Output: 1
Advanced Topics
Recursive Functions
A function that calls itself is recursive. Useful for tasks like traversing directories, calculating factorials, or solving mathematical problems.
Example: Factorial Function
factorial() {
local n=$1
if (( n == 0 )); then
echo 1 # Base case: 0! = 1
else
echo $(( n * $(factorial $((n - 1))) )) # Recursive call
fi
}
echo "5! = $(factorial 5)" # Output: 5! = 120 (5*4*3*2*1)
Example: Directory Traversal (Recursive)
list_files() {
local dir=$1
for item in "$dir"/*; do
if [ -d "$item" ]; then # If item is a directory
echo "DIR: $item"
list_files "$item" # Recurse into subdirectory
elif [ -f "$item" ]; then # If item is a file
echo "FILE: $item"
fi
done
}
list_files "./my_project" # List all files/subdirs in ./my_project
Arrays in Functions
Bash arrays can be passed to functions, but they require special handling (Bash functions don’t support array return values natively).
Passing Arrays to Functions
Use "${array[@]}" to pass array elements as separate arguments, then reconstruct the array inside the function with local args=("$@").
print_array() {
local arr=("$@") # Reconstruct array from arguments
echo "Array elements: ${arr[*]}" # ${arr[*]} joins elements with spaces
}
fruits=("apple" "banana" "cherry")
print_array "${fruits[@]}" # Pass array as separate arguments
Output: Array elements: apple banana cherry
“Returning” Arrays
Since functions can’t return arrays, use a global array or command substitution with a delimiter (e.g., :) to simulate returns:
get_even_numbers() {
local start=$1
local end=$2
local evens=()
for ((i=start; i<=end; i++)); do
if ((i % 2 == 0)); then
evens+=($i) # Add to array
fi
done
echo "${evens[*]}" # Output elements separated by spaces
}
# Capture output and split into array
result_str=$(get_even_numbers 1 10)
evens=($result_str) # Split string into array
echo "Even numbers: ${evens[@]}" # Output: 2 4 6 8 10
Global vs. Local Variables: Pitfalls to Avoid
Accidentally modifying global variables is a common Bash scripting mistake.
Pitfall: Unintended Global Modification
config="default"
set_config_bad() {
config="custom" # Modifies global config!
}
set_config_bad
echo "Config: $config" # Output: custom (unintended change)
Fix: Use local
config="default"
set_config_good() {
local config="custom" # Local variable (no global change)
echo "Local config: $config"
}
set_config_good
echo "Global config: $config" # Output: default (unchanged)
Best Practices
-
Naming Conventions
- Use UPPERCASE for environment variables (e.g.,
PATH,LOG_DIR). - Use lowercase for local variables and functions (e.g.,
user_name,process_file). - Avoid reserved words (e.g.,
if,for,function).
- Use UPPERCASE for environment variables (e.g.,
-
Quote Variables
Always quote variables ("$var") to prevent word splitting and globbing:filename="my file.txt" cat "$filename" # Works (quotes preserve spaces) # cat $filename → Error: cat: my: No such file or directory -
Use
localin Functions
Prevent global side effects by declaring variables aslocalinside functions. -
Avoid Global Variables
Pass data to functions via parameters and return data via output/exit status instead of relying on globals. -
Comment Functions
Document what a function does, its parameters, and return values:# Calculate the sum of two numbers # Args: $1 (int), $2 (int) # Output: Sum of $1 and $2 sum() { echo $(( $1 + $2 )) } -
Test with
set -euo pipefail
Addset -euo pipefailat the top of scripts to catch errors early:-e: Exit on error.-u: Treat undefined variables as errors.-o pipefail: Exit if any command in a pipeline fails.
Conclusion
Bash variables and functions are the building blocks of clean, maintainable scripts. Variables let you store and manipulate data, while functions encapsulate logic for reuse. By mastering scoping, parameters, recursion, and best practices like local variables and quoting, you’ll write scripts that are robust, readable, and easy to debug.
The key to mastery is practice: experiment with recursive functions, refactor messy scripts into modular functions, and always test edge cases. With these tools, you’ll transform from a Bash novice to a scripting pro.
Reference
- GNU Bash Manual
- Bash Hackers Wiki
- ShellCheck (static analysis tool for Bash scripts)
- Advanced Bash-Scripting Guide