Bash Variables, Expressions, and Statements
In the world of shell scripting, variables serve as named containers used to store strings or numeric values. They are the building blocks of any dynamic script, allowing you to manage data efficiently.
Naming and Assignment
Variable names in Bash must consist of letters, numbers, and underscores, and they cannot begin with a digit. A critical rule for beginners is the syntax of assignment: you must not include spaces around the equals sign.
name="Bash"
count=10
path="/usr/local/bin"
If you write name = "Bash", Bash will attempt to execute name as a command, leading to an error.
Referencing Variables
When accessing a variable's value, it is a best practice to wrap it in double quotes, such as "$var" or "${var}". This prevents the shell from performing unintended word splitting or globbing (pattern expansion) if the variable contains spaces or special characters.
echo "Hello, $name"
echo "The length of the name is: ${#name}"
Special Variables
Bash provides several built-in special variables that offer metadata about the current execution environment:
$0: The name of the script or the shell itself.$1,$2, etc.: Positional parameters (arguments passed to the script).$#: The total number of positional parameters.$?: The exit status of the most recently executed command (0 indicates success).$$: The Process ID (PID) of the current shell.$@and$*: Represent all positional parameters.
Scope and Visibility
Variables can be managed based on their intended reach:
- Environment Variables: Use
exportto make a variable available to child processes. - Read-only Variables: Use
readonlyto prevent a variable from being modified or unset. - Local Variables: Inside functions, use the
localkeyword to ensure the variable exists only within that function's scope, preventing accidental modification of global variables.
export APP_ENV="production"
readonly MAX_RETRY=3
demo_function() {
local msg="Internal message"
echo "$msg"
}
Mastering Parameter Expansion
Parameter expansion is one of Bash's most potent features. It allows you to manipulate strings and handle default values directly within the variable reference, eliminating the need to call external utilities like sed or awk.
Default Values and Error Handling
You can define fallback behaviors if a variable is unset or empty:
${var:-default}: Returnsdefaultifvaris unset or null, but does not assign it.${var:=default}: Returnsdefaultand assigns it tovarif it was unset or null.${var:+alt}: Returnsaltonly ifvaris set and not null.${var:?message}: Outputs an error message and exits the script ifvaris unset or null.
user="${USER:-guest}"
echo "Current user: $user"
# This will exit the script if the first argument is missing
file_path="${1:?Error: Please provide a filename.}"
Substrings and Length
Bash allows you to extract specific portions of a string using offsets and lengths:
text="Bash Script"
echo "${#text}" # Returns the length: 11
echo "${text:0:4}" # Returns "Bash" (starting at index 0, length 4)
echo "${text: -6}" # Returns "Script" (negative offset; note the space before the minus)
Pattern Matching and Replacement
You can strip prefixes or suffixes from a string using pattern matching:
${file#*/}: Removes the shortest match of the pattern from the beginning.${file##*/}: Removes the longest match from the beginning.${file%.*}: Removes the shortest match of the pattern from the end.${file%%.*}: Removes the longest match from the end.
file="/tmp/demo.tar.gz"
echo "${file#*/}" # tmp/demo.tar.gz
echo "${file##*/}" # demo.tar.gz
echo "${file%%.*}" # /tmp/demo
For string replacement:
${str/pattern/replacement}: Replaces the first occurrence.${str//pattern/replacement}: Replaces all occurrences.
str="foo bar foo"
echo "${str/foo/baz}" # baz bar foo
echo "${str//foo/baz}" # baz bar baz
Case Transformation and Indirect Referencing
Bash can also handle case conversion and indirect variable access:
word="bash"
echo "${word^}" # Bash (capitalize first letter)
echo "${word^^}" # BASH (capitalize all)
echo "${word,}" # bash (lowercase first letter)
echo "${word,,}" # bash (lowercase all)
# Indirect expansion
var_name="name"
name="Alice"
echo "${!var_name}" # Alice
Arithmetic and Command Substitution
Arithmetic Expressions
Bash supports integer arithmetic through two primary methods:
- Arithmetic Expansion
$((expression)): Used to evaluate an expression and return the result. - Arithmetic Command
((expression)): Used to perform operations, often for control flow.
In an arithmetic context, the $ prefix for variables is optional.
a=5
b=3
sum=$((a + b))
((a++)) # Increment a
((b += 2)) # Add 2 to b
echo "$sum $a $b" # Output: 8 6 5
Note that ((...)) returns an exit status: it returns 0 (true) if the expression evaluates to a non-zero value, and 1 (false) if it evaluates to zero.
Command Substitution
Command substitution allows you to capture the output of a command and assign it to a variable or use it in a command. The modern syntax $(command) is highly preferred over the legacy backticks (`command`).
today=$(date +%F)
files=$(ls | wc -l)
echo "Today is $today, and there are $files files in this directory."
Tip: If you need to preserve newlines in the output, always wrap the substitution in double quotes: content="$(cat file.txt)".
Conditional Expressions and Testing
Bash provides several ways to perform tests, but the double bracket syntax [[ ... ]] is the modern standard. It is more robust than the older [ (test) command because it supports regular expressions and does not require as much quoting to prevent errors.
String and Numeric Tests
- Strings: Use
-zfor empty strings,-nfor non-empty strings, and==or!=for equality/inequality. - Numbers: Use
-eq(equal),-ne(not equal),-lt(less than),-le(less or equal),-gt(greater than), and-ge(greater or equal).
File Tests
Bash can inspect the filesystem directly:
-e: File exists.-f: Is a regular file.-d: Is a directory.-r,-w,-x: File is readable, writable, or executable.
if [[ -n "$name" && "$name" =~ ^[A-Za-z]+$ ]]; then
echo "Valid name provided."
elif [[ -d "$path" ]]; then
echo "Path is a directory."
else
echo "Condition not met."
fi
For multi-branch logic, the case statement is more readable than multiple if-elif blocks:
case "$1" in
start) echo "Starting service..." ;;
stop) echo "Stopping service..." ;;
*) echo "Usage: $0 {start|stop}" ;;
esac
Advanced Variable Attributes
The declare (or typeset) command allows you to define specific attributes for variables, which can enforce data types or behaviors.
declare -i: Treats the variable as an integer.declare -a: Defines an indexed array.declare -A: Defines an associative array (key-value pairs).declare -r: Makes the variable read-only.declare -x: Exports the variable to the environment.declare -n: Creates a nameref (indirect reference).
declare -i num=10
declare -a arr=(a b c)
declare -A map
declare -r PI=3.14
Professional Best Practices
To write secure, maintainable, and professional Bash scripts, follow these guidelines:
- Always Quote Variables: Use
"$var"to prevent unexpected behavior caused by spaces or wildcards. - Use Braces for Clarity: Use
${var}when the variable is adjacent to other characters (e.g.,"${prefix}_suffix") to avoid ambiguity. - Enable Strict Mode: Start your scripts with
set -uto treat unset variables as errors, andset -o pipefailto ensure errors in a pipeline are correctly captured. - Prefer Modern Syntax: Use
[[ ... ]]for tests and$(( ... ))for arithmetic. - Encapsulate with
local: Always declare variables inside functions aslocalto prevent side effects in the global scope. - Avoid
eval: Never useevalwith untrusted input, as it opens your script to command injection vulnerabilities.