Variables & Parameters
Assigning Variables
A variable is a name bound to a string. Bash has no types: everything is text until arithmetic expansion reinterprets it as a number.
Assignment Syntax
greeting="hello" # assign (no $ on the left side, ever)
count=7 # digits are still a string until you do arithmetic
greeting="hi $USER" # expand on the right side
No Spaces Around =
Spaces around = turn the assignment into a command named after the left word.
name = "x" # ERROR: bash runs a command "name" with args "=" "x"
name="x" # correct
readonly and declare
readonly MAX=100 # cannot be reassigned afterwards
declare -i n=0 # integer attribute: n=3+4 stores 7, not "3+4"
declare -r GREET="hi"
declare -l lower="ABC" # -l forces lowercase: "abc"
declare -p MAX # print the variable and its attributes
unset greeting # remove the binding
Scope
Shell Variables vs Environment
A shell variable lives only in the current shell. An environment variable is copied into every child process. Without export, a child program simply cannot see your variable.
LOCAL_VAR="x" # shell variable only
export APP_ENV="prod" # child processes inherit this
env | grep APP_ENV # shows APP_ENV because it was exported
export
export PATH="$HOME/bin:$PATH" # common pattern
export EDITOR=vim
declare -x LOG_LEVEL=info # -x is the export attribute
local (inside functions)
Inside a function, local limits the variable to that function call. Without it you would overwrite a caller's variable.
greet() {
local name="$1" # scoped to this function
echo "hello $name"
}
greet bob
echo "$name" # empty: 'name' never leaked out
Special Parameters
Positional Parameters
| Form | Meaning |
|---|---|
$0 | Name of the script or shell |
$1 … $9 | First … ninth argument |
${10} | Arguments past nine need braces |
$# | Number of arguments |
"$@" | All arguments, each still quoted separately |
"$*" | All arguments as one word, joined by the first IFS char |
printf 'arg%d=%s\n' 0 "$0"
for a in "$@"; do echo "arg: $a"; done # correct: preserves spaces
for a in $*; do echo "arg: $a"; done # wrong: splits on spaces
$? — Last Exit Status
false; echo $? # 1
true; echo $? # 0
$$, $! and $_
echo "$$" # PID of the current shell
sleep 5 &
echo "$!" # PID of the most recent background job
${BASH_SOURCE[0]}
Inside a sourced file, $0 is the parent script. ${BASH_SOURCE[0]} gives the file's own path, which is what a library needs to find its siblings.
Parameter Expansion Forms
Defaults and Errors
echo "${1:-default}" # use default if unset OR empty
echo "${1-default}" # use default only if unset
: "${CONFIG:?config path is required}" # abort with a message if unset
Substring and Length
s="hello world"
echo "${#s}" # 11 (length)
echo "${s:6}" # world
echo "${s:0:5}" # hello
Trim and Replace
path="/a/b/c.txt"
echo "${path##*/}" # c.txt (remove longest prefix up to last /)
echo "${path%/*}" # /a/b (remove shortest suffix from last /)
echo "${path%.txt}" # /a/b/c (remove a suffix)
file="a.b.c"
echo "${file//./-}" # a-b-c (replace all)
echo "${file%%.*}" # a (strip first extension onward)
basename or dirname. ##/%% are greedy; #/% are minimal.
Naming Conventions
- Names may contain letters, digits and underscores, and cannot start with a digit.
- Use
UPPER_CASEfor exported environment variables andlower_casefor script-local variables. - Avoid overriding important variables such as
PATH,IFSandHOME.
Pitfalls
Unset vs Empty
${x:-d} treats unset and empty alike. ${x-d} only substitutes when unset. Choose deliberately.
Case Sensitivity
Path, path and PATH are three different variables. Bash never guesses which you meant.