Create Bash Function with Arguments – Bash Scripting Guide

bashbash-script

I have a bash function to set the $PATH like this —

assign-path()
{
    str=$1
    # if the $PATH is empty, assign it directly.
    if [ -z $PATH ]; then
        PATH=$str;
    # if the $PATH does not contain the substring, append it with ':'.
    elif [[ $PATH != *$str* ]]; then
        PATH=$PATH:$str;
    fi
}

But the problem is, I have to write different function for different variables (for example, another function for $CLASSPATH like assign-classpath() etc.). I could not find a way to pass argument to the bash function so that I can access it by reference.

It would be better if I had something like —

assign( bigstr, substr )
{
    if [ -z bigstr ]; then
        bigstr=substr;
    elif [[ bigstr != *str* ]]; then
        bigstr=bigstr:substr;
    fi
}

Any idea, how to achieve something like above in bash?

Best Answer

In bash you can use ${!varname} to expand the variable referenced by the contents of another. Eg:

$ var=hello
$ foo () { echo "${!1}"; }
$ foo var
hello

From the man page:

${!prefix*}
${!prefix@}
       Names matching prefix.  Expands to the names of variables whose names
       begin with prefix, separated by the first character of the IFS special
       variable.  When @ is used  and the expansion appears within double quotes,
       each variable name expands to a separate word.

Also, to set a variable referenced by the contents (without the dangers of eval), you can use declare. Eg:

$ var=target
$ declare "$var=hello"
$ echo "$target"
hello

Thus, you could write your function like this (take care because if you use declare in a function, you must give -g or the variable will be local):

shopt -s extglob

assign()
{
  target=$1
  bigstr=${!1}
  substr=$2

  if [ -z "$bigstr" ]; then
    declare -g -- "$target=$substr"
  elif [[ $bigstr != @(|*:)$substr@(|:*) ]]; then
    declare -g -- "$target=$bigstr:$substr"
  fi
}

And use it like:

assign PATH /path/to/binaries

Note that I have also corrected an bug where if substr is already a substring of one of the colon separated members of bigstr, but not its own member, then it wouldn't be added. For example, this would allow adding /bin to a PATH variable already containing /usr/bin. It uses the extglob sets to match either the beginning/end of the string or a colon then anything else. Without extglob, the alternative would be:

[[ $bigstr != $substr && $bigstr != *:$substr &&
   $bigstr != $substr:* && $bigstr != *:$substr:* ]]
Related Question