Variables and quoting
$name expands a variable; ${name} makes the boundary explicit in text such as ${name}_log. Quote "$name" so a value containing spaces stays one argument and glob characters are not unexpectedly expanded. Single quotes keep text literal: '$HOME' prints the five characters, while "$HOME" prints your home path.
A plain variable exists only in the current shell. export APP_ENV=prod turns it into an environment variable that child processes inherit. env lists the environment; common entries are HOME, USER, PATH, and PWD. readonly protects a value and unset name removes it.
flowchart LR
L["Command line<br/>cp $src $dst"] --> PE["Parameter expansion<br/>${name}"]
PE --> CS["Command substitution<br/>$(date +%F)"]
CS --> WS["Word splitting<br/>unquoted only"]
WS --> GL["Globbing<br/>* ? [ ]"]
GL --> RUN["Run the command<br/>final arguments"]
%% step L: Bash reads the line before running anything.
%% step PE: Variables are replaced by their values.
%% step CS: $( ) runs a command and inserts its output.
%% step WS: Unquoted results are split on spaces, so "my file.txt" would become two words.
%% step GL: Then wildcards are expanded to filenames.
%% step RUN: Quoting "$var" stops splitting and globbing so the data keeps its boundaries.
Command and arithmetic substitution
today=$(date +%F) stores a command's output. $((count + 1)) evaluates integer arithmetic; Bash has no built-in floating point, so tools such as bc or awk handle decimals. The older backtick form `date` still works but nests poorly.
Brace and parameter expansion
Brace expansion generates text before a command runs: {1..3} becomes 1 2 3. It does not read runtime variables, so {1..$n} does not make a dynamic range. Parameter expansion operates on values: ${food:-Cake} supplies a default without assigning it, while ${food:=Cake} also assigns the default.
| Expansion | Result with name=John |
|---|---|
${#name} |
4 (length) |
${name/J/j} |
john |
${name:0:2} |
Jo |
${name::-1} |
Joh |
Prefix and suffix removal is handy for file names: with file=backup.tar.gz, ${file%.gz} gives backup.tar, ${file%%.*} gives backup, and ${path##*/} keeps only the part after the last slash, like basename.
Arrays
fruits=(Apple Banana Orange)
fruits+=(Watermelon)
printf '%s\n' "${fruits[@]}"
printf '%s\n' "${#fruits[@]}"
Quoted ${fruits[@]} preserves each element as a separate argument. ${#fruits[@]} is the element count; ${#fruits} is only the length of element zero. In modern Bash, a negative index such as ${fruits[-1]} selects the final element.
Bash 4 and later also support associative arrays keyed by strings:
declare -A port=([http]=80 [https]=443)
echo "${port[https]}"
echo "${!port[@]}" # the keys