Parameter expansion tricks

Bash · Practical · 12 min

These expansions replace many calls to sed, basename and dirname and make scripts faster and shorter.

${...} can do much more than read a variable. The forms you will use constantly:

  • Defaults: ${var:-def} gives def if var is unset or empty; ${var:=def} also assigns it; ${var:?message} aborts the script with the message; ${var:+alt} gives alt only when var is set.
  • Remove a prefix/suffix pattern: # removes the shortest matching prefix, ## the longest; % and %% do the same at the end. So ${path##*/} is the base name and ${path%/*} the directory, ${file%.*} drops the extension.
  • Replace: ${var/old/new} first match, ${var//old/new} all matches; patterns are globs, not regexes.
  • Length and substrings: ${#var} and ${var:offset:length}; negative offsets need a space: ${var: -3}.
  • Case (bash 4+): ${var^} capitalizes the first letter, ${var^^} upper-cases all, ${var,,} lower-cases.

Without the colon (${var-def}) the default applies only when var is unset, not when it is empty. That distinction matters for optional settings where an empty value is meaningful.

These expansions run inside the shell, so they are much faster in loops than spawning basename or sed for every item.

Example

path="/var/log/nginx/access.log.gz"
echo "${path##*/}"        # access.log.gz        (basename)
echo "${path%/*}"         # /var/log/nginx       (dirname)
echo "${path%.gz}"        # /var/log/nginx/access.log
echo "${path#*.}"         # log.gz               (up to the first dot)
echo "${#path}"           # 28
echo "${path/log/LOG}"    # /var/LOG/nginx/access.log.gz
echo "${path//log/LOG}"   # /var/LOG/nginx/access.LOG.gz
echo "${path:5:3}"        # log
echo "${path: -2}"        # gz
unset port
echo "${port:-8080}"      # 8080  (port is still unset)
echo "${port:=3000} $port"     # 3000 3000
name=ada
echo "${name^} ${name^^}" # Ada ADA
# : "${API_KEY:?must be set}"  -> aborts: API_KEY: must be set

Remember

  • ${v:-def} default, ${v:=def} assign, ${v:?msg} require
  • # and ## strip prefixes, % and %% strip suffixes
  • ${v//a/b} replaces with glob patterns; ${#v} is the length

Watch out for

  • Writing ${var:-3} when you meant the last 3 characters: you need ${var: -3} with a space.

Check yourself

  1. With f=archive.tar.gz, what is ${f%%.*}?
  2. What does ${name:-guest} give when name is set to the empty string?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion