Bash: Indirectly referencing variables

Sometimes it comes in handy to modify a variable in a function by name.

Usually the first tool people reach for in this case eval:

# sets a variable to a new value
setvar() {
  eval $(printf "%s=%q" "$1" "$2")
}

setvar x 2
printf "x=%s\n" "$x" # prints x=2

This works but making sure that everything is quoted correctly is cumbersome.

Bash supports a safer way for this: namerefs

To use a nameref you must declare a variable as a nameref using the -n argument.
After that, any changes made to the nameref are actually applied to the variable it is referring to.

Here's setvar written using namerefs:

setvar() {
  local -n varname="$1"
  varname="$2"
}

setvar x 3
printf "x=%s\n" # prints x=3

# it also supports arrays:
declare -a x=()
setvar 'x[0]' 3
setvar 'x[1]' hello
setvar 'x[2]' world
declare -p x
# prints: declare -a x=([0]="3" [1]="hello" [2]="world")

Adding directories to PATH-like variables

Besides PATH, there are other colon-separated variables that are used by various programs for lookup purposes, such as man's MANPATH or Ruby's RUBYLIB.

Often these are modified in your ~/.bashrc like this:

export PATH="$HOME/.yarn/bin:$PATH"

That works until you source your ~/.bashrc again, for example because you edited it: you'll end up with the same entry twice on your PATH.

Let's use namerefs to set PATH-like variables idempotently: calling add_to_path with the same path twice will not change the resulting path.

add_to_path() {
  local -n pathvar="$1"
  local dir="$2"
  # set IFS to : to split by colons
  local IFS=:
  local -a pathparts=($pathvar)

  for part in "${pathparts[@]}"; do
    if [[ "$part" == "$dir" ]]; then
      return
    fi
  done

  pathvar="$dir:$pathvar"
}

Trying it out in the shell, we can see that it works as expected:

$ P=a:b
$ add_to_path P first
$ [[ "$P" == "first:a:b" ]] && printf "OK\n"
OK
$ add_to_path P first
$ [[ "$P" == "first:a:b" ]] && printf "OK\n"
OK

You'll only receive email when they publish something new.

More from Dario Hamidi
All posts