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 eval
does 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`