A bash function that takes argument like other languages?

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:* ]]

New in bash 4.3, is the -n option to declare & local:

func() {
    local -n ref="$1"
    ref="hello, world"
}

var='goodbye world'
func var
echo "$var"

That prints out hello, world.


You can use eval to set a parameter. A description of this command can be found here. The following usage of eval is wrong:

wrong(){
  eval $1=$2
}

With respect to the additional evaluation evaldoes you should use

assign(){
  eval $1='$2'
}

Check the results of using these functions:

$ X1='$X2'
$ X2='$X3'
$ X3='xxx'
$ 
$ echo :$X1:
:$X2:
$ echo :$X2:
:$X3:
$ echo :$X3:
:xxx:
$ 
$ wrong Y $X1
$ echo :$Y:
:$X3:
$ 
$ assign Y $X1
$ echo :$Y:
:$X2:
$ 
$ assign Y "hallo world"
$echo :$Y:
:hallo world:
$ # the following may be unexpected
$ assign Z $Y
$ echo ":$Z:"
:hallo:
$ # so you have to quote the second argument if its a variable
$ assign Z "$Y"
$ echo ":$Z:"
:hallo world:

But you can achieve your goal without the usage of eval. I prefer this way that is more simple.

The following function makes the substitution in the right way (I hope)

augment(){
  local CURRENT=$1
  local AUGMENT=$2
  local NEW
  if [[ -z $CURRENT ]]; then
    NEW=$AUGMENT
  elif [[ ! ( ( $CURRENT = $AUGMENT ) || ( $CURRENT = $AUGMENT:* ) || \
    ( $CURRENT = *:$AUGMENT ) || ( $CURRENT = *:$AUGMENT:* ) ) ]]; then
    NEW=$CURRENT:$AUGMENT
  else
    NEW=$CURRENT
    fi
  echo "$NEW"
}

Check the following output

augment /usr/bin /bin
/usr/bin:/bin

augment /usr/bin:/bin /bin
/usr/bin:/bin

augment /usr/bin:/bin:/usr/local/bin /bin
/usr/bin:/bin:/usr/local/bin

augment /bin:/usr/bin /bin
/bin:/usr/bin

augment /bin /bin
/bin


augment /usr/bin: /bin
/usr/bin::/bin

augment /usr/bin:/bin: /bin
/usr/bin:/bin:

augment /usr/bin:/bin:/usr/local/bin: /bin
/usr/bin:/bin:/usr/local/bin:

augment /bin:/usr/bin: /bin
/bin:/usr/bin:

augment /bin: /bin
/bin:


augment : /bin
::/bin


augment "/usr lib" "/usr bin"
/usr lib:/usr bin

augment "/usr lib:/usr bin" "/usr bin"
/usr lib:/usr bin

Now you can use the augment function in the following way to set a variable:

PATH=`augment PATH /bin`
CLASSPATH=`augment CLASSPATH /bin`
LD_LIBRARY_PATH=`augment LD_LIBRARY_PATH /usr/lib`