Variables in the shell are simpler than in most languages and easier to break, usually in the same line: name=value assigns, $name expands — and the space around the = is a bug, not style.

Assignment and expansion #

Assign, expand, and the classic mistake
$ name="aospix"
$ echo "$name desktop"
aospix desktop
$ name = "aospix"      # the space makes bash run a command named name
bash: name: command not found

No spaces around =. Names are letters, digits, and underscores, not starting with a digit. Read-only values get readonly, and unset name removes one.

Scope: shell, environment, child #

A variable exists in the current shell. Exporting it promotes it to the environment, where child processes inherit it:

Local vs exported
$ APP_ENV=dev bash -c 'echo $APP_ENV'    # not exported: empty
$ export APP_ENV=dev
$ bash -c 'echo $APP_ENV'                # inherited: dev

That distinction is why scripts see some settings and not others, and it is the whole story behind PATH and tool configuration like EDITOR.

Parameter expansion: the free toolbox #

${...} is more than $name with braces — it is the shell's built-in string workshop:

FormMeaning
${name:-fallback}Use fallback if unset or empty
${name:=fallback}Assign fallback if unset or empty
${name:?message}Fail loudly if unset
${#name}Length
${name%.txt}Strip the shortest .txt suffix
${name#./}Strip a leading ./
${name/a/b}Replace first a with b

The default forms are the scripting workhorses — required-argument guards without an if:

Demand an argument or fail
config_file="${1:?usage: deploy.sh <config>}"
echo "deploying with $config_file"

The suffix strip is how bulk renames work: "${f%.jpeg}.jpg".

Special variables worth knowing #

$? is the last exit code, $1..$9 are script arguments, $# their count, $$ the current PID, and $0 the script's own name — the ingredients of every usage message.

Strings are only the start: arrays hold lists, and strict mode makes unset variables loud. The scripting series continues.