Create, read, and modify Bash variables. Learn the quoting rules that bite everyone, the difference between shell and environment variables, scope with export and local, and the habits that keep scripts safe.
Why: a variable stores a value you reuse. The rule that trips everyone up: NO spaces around the = when assigning. Read the value back by putting $ in front of the name. Note: name=value sets it, name (no $) on its own would try to run a command.
#!/usr/bin/env bash
name="Ada" # assign — no spaces around =
greeting="Hello"
echo "$greeting, $name" # read with $ -> Hello, Ada
name="Grace" # reassign — same syntax
echo "$greeting, $name" # -> Hello, GraceWhy: an unquoted $var is split on spaces and expanded as a filename glob. If a filename contains a space, an unquoted variable silently becomes two arguments — the single most common Bash bug. Note: "double quotes" still expand $variables; 'single quotes' are fully literal and expand nothing.
#!/usr/bin/env bash
file="my notes.txt"
rm $file # WRONG: runs rm my notes.txt (two files!)
rm "$file" # RIGHT: one argument, "my notes.txt"
echo "$file" # double quotes: expands -> my notes.txt
echo '$file' # single quotes: literal -> $fileWhy: $(...) runs a command and substitutes its output right into your line, so you can capture results into variables. Note: prefer $(...) over the older backtick `...` form — it nests cleanly and reads better.
#!/usr/bin/env bash
today=$(date +%Y-%m-%d) # capture command output
file_count=$(ls | wc -l) # count files in this folder
echo "Today is $today"
echo "There are $file_count entries here"Why: a plain variable lives only in the current shell. export turns it into an environment variable, which is inherited by every command and child script you launch. That is how settings like PATH and EDITOR reach the programs you run.
A normal variable — visible here, NOT to child processes
greeting="hi"Export it — now child scripts and commands inherit it
export EDITOR="nano"See all exported environment variables
printenv | headWhy: variables are global across the whole script by default — a name set anywhere is visible everywhere, which causes surprise clashes. Inside a function, declare with local so the variable exists only there and cannot leak out or clobber a caller.
#!/usr/bin/env bash
count=10 # global to the whole script
bump() {
local count=99 # local — separate from the global
echo "inside: $count"
}
bump # inside: 99
echo "outside: $count" # outside: 10 (unchanged)Why: ${var:-default} uses a fallback when var is unset or empty, so a missing value does not break the script. Best practice: quote every expansion, use lowercase names for your own variables (UPPERCASE is convention for environment variables), and brace ${name} when it touches other text.
#!/usr/bin/env bash
# Use a default when the variable is unset or empty
name="${1:-world}" # $1 is the first argument; fall back to "world"
echo "Hello, ${name}!"
# Braces disambiguate the name from surrounding text
env="prod"
echo "deploy-${env}-server" # -> deploy-prod-server