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

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

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
    elif [[ bigstr != *str* ]]; then

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

  • What "other languages"?
    – choroba
    Apr 1, 2014 at 22:34
  • well, I tried to mean if bash allows "pass by reference" like in c/java etc.
    – ramgorur
    Apr 1, 2014 at 22:39
  • 1
    assign-path /abc will not append /abc to PATH if $PATH already contains /abc/def, /abcd, /def/abc etc. Especially you can't add /bin if PATH already contains /usr/bin.
    – miracle173
    Apr 2, 2014 at 5:33
  • @miracle173 - this is true, what you need to do is split $PATH and negate test against your arguments like: add=/bin dir=/usr/bin ; [ -z "${dir%"$add"}" ] || dir="${dir}:${add}". In my answer I do it this way with as many arguments as you like only using IFS=:.
    – mikeserv
    Apr 2, 2014 at 6:39
  • Related to the specific (instance) issue of adding values to colon-separated lists: How can I cleanly add to $PATH? and Add directory to $PATH if it’s not already there (on Super User). Jul 12, 2019 at 19:22

9 Answers 9


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

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

From the man page:

       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"

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


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

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:* ]]
  • -g in declare is not available in older version of bash, is there any way so that I can make this backward compatible?
    – ramgorur
    Jul 22, 2015 at 5:57
  • 2
    @ramgorur, you could use export to put it in your environment (at the risk of overwriting something important) or eval (various issues including security if you are not careful). If using eval you should be ok if you do it like eval "$target=\$substr". If you forget the \ though, it will potentially execute a command if there is a space in the contents of substr.
    – Graeme
    Jul 22, 2015 at 11:16

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.

  • The only problem with namerefs in Bash is that you can't have a nameref (in a function for example) referencing a variable (outside the function) of the same name as the nameref itself. This may be fixed for release 4.5 though.
    – Kusalananda
    Jan 19, 2017 at 9:18

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

  eval $1=$2

With respect to the additional evaluation evaldoes you should use

  eval $1='$2'

Check the results of using these functions:

$ X1='$X2'
$ X2='$X3'
$ X3='xxx'
$ echo :$X1:
$ echo :$X2:
$ echo :$X3:
$ wrong Y $X1
$ echo :$Y:
$ assign Y $X1
$ echo :$Y:
$ assign Y "hallo world"
$echo :$Y:
:hallo world:
$ # the following may be unexpected
$ assign Z $Y
$ echo ":$Z:"
$ # 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)

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

Check the following output

augment /usr/bin /bin

augment /usr/bin:/bin /bin

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

augment /bin:/usr/bin /bin

augment /bin /bin

augment /usr/bin: /bin

augment /usr/bin:/bin: /bin

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

augment /bin:/usr/bin: /bin

augment /bin: /bin

augment : /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`
  • Even your eval statement is wrong. This, for example: v='echo "OHNO!" ; var' ; l=val ; eval $v='$l' - would echo "OHNO! before assigning var. You could "${v##*[;"$IFS"]}='$l'" to ensure that the string can't expand to anything that won't be evaluated with the =.
    – mikeserv
    Apr 2, 2014 at 9:42
  • @mikeserv thank you for your comment but I think that is not a valid example. The first argument of the assign script should be a variable name or a variable that contains a variable name that is used on the left hand side of the = assignement statement. You can argue that my scrip does not check its argument. That is true. I do not even check if there is any argument or if the number of arguments is valid. But this was by intention. The OP can add such checks if he wants.
    – miracle173
    Apr 2, 2014 at 13:04
  • @mikeserv: I think that your proposal to transform the first argument silently to a valid variable name is not a good idea: 1) some variable is set/overwritten that was not intended by the user. 2) the error is hidden from the user of the function. That is never a good idea. One should simply raise an error if that happens.
    – miracle173
    Apr 2, 2014 at 13:06
  • @mikeserv: It is interesting when one wants to use your variable v (better its value) as second argument of the assign function. So its value should be on the right hand side of an assignement. It is necessaary to quote the argument of the function assign. I added this subtlety to my posting.
    – miracle173
    Apr 2, 2014 at 13:09
  • Probably true - and you're not actually using eval in your final example - which is wise - so it doesn't really matter. But what I'm saying is that any code that uses eval and takes user input is inherently risky - and if you did use your example, I could make the function designed to change my path to rm my path with little effort.
    – mikeserv
    Apr 2, 2014 at 13:10

With a few tricks you can actually pass named parameters to functions, along with arrays (tested in bash 3 and 4).

The method I developed allows you to access parameters passed to a function like this:

testPassingParams() {

    @var hello
    l=4 @array anArrayWithFourElements
    l=2 @array anotherArrayWithTwo
    @var anotherSingle
    @reference table   # references only work in bash >=4.3
    @params anArrayOfVariedSize

    test "$hello" = "$1" && echo correct
    test "${anArrayWithFourElements[0]}" = "$2" && echo correct
    test "${anArrayWithFourElements[1]}" = "$3" && echo correct
    test "${anArrayWithFourElements[2]}" = "$4" && echo correct
    # etc...
    test "${anotherArrayWithTwo[0]}" = "$6" && echo correct
    test "${anotherArrayWithTwo[1]}" = "$7" && echo correct
    test "$anotherSingle" = "$8" && echo correct
    test "${table[test]}" = "works"
    table[inside]="adding a new value"
    # I'm using * just in this example:
    test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct

fourElements=( a1 a2 "a3 with spaces" a4 )
twoElements=( b1 b2 )
declare -A assocArray

testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..."

test "${assocArray[inside]}" = "adding a new value"

In other words, not only you can call your parameters by their names (which makes up for a more readable core), you can actually pass arrays (and references to variables - this feature works only in bash 4.3 though)! Plus, the mapped variables are all in the local scope, just as $1 (and others).

The code that makes this work is pretty light and works both in bash 3 and bash 4 (these are the only versions I've tested it with). If you're interested in more tricks like this that make developing with bash much nicer and easier, you can take a look at my Bash Infinity Framework, the code below was developed for that purpose.

Function.AssignParamLocally() {
    local commandWithArgs=( $1 )
    local command="${commandWithArgs[0]}"


    if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]]
        return 0

    if [[ "$command" != "local" ]]

    local varDeclaration="${commandWithArgs[1]}"
    if [[ $varDeclaration == '-n' ]]
    local varName="${varDeclaration%%=*}"

    # var value is only important if making an object later on from it
    local varValue="${varDeclaration#*=}"

    if [[ ! -z $assignVarType ]]
        local previousParamNo=$(expr $paramNo - 1)

        if [[ "$assignVarType" == "array" ]]
            # passing array:
            execute="$assignVarName=( \"\${@:$previousParamNo:$assignArrLength}\" )"
            eval "$execute"
            paramNo+=$(expr $assignArrLength - 1)

            unset assignArrLength
        elif [[ "$assignVarType" == "params" ]]
            execute="$assignVarName=( \"\${@:$previousParamNo}\" )"
            eval "$execute"
        elif [[ "$assignVarType" == "reference" ]]
            eval "$execute"
        elif [[ ! -z "${!previousParamNo}" ]]
            eval "$execute"


Function.CaptureParams() {

alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"\$BASH_COMMAND\" \"\$@\"; [[ \$assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; '
alias @param='@trapAssign local'
alias @reference='_type=reference @trapAssign local -n'
alias @var='_type=var @param'
alias @params='_type=params @param'
alias @array='_type=array @param'
assign () 
    if [ -z ${!1} ]; then
        eval $1=$2
        if [[ ${!1} != *$2* ]]; then
            eval $1=${!1}:$2

$ echo =$x=
$ assign x y
$ echo =$x=
$ assign x y
$ echo =$x=
$ assign x z
$ echo =$x=

Does this fit?

  • hi, I tried to do it like yours, but not working, sorry for being a newbie. Could you please tell me what is wrong with this script?
    – ramgorur
    Apr 1, 2014 at 22:36
  • 1
    Your usage of eval is susceptible to arbitrary command execution.
    – Chris Down
    Apr 2, 2014 at 3:24
  • You have an idea to make zhe eval lines safer? Normally, when I think I need eval, I decide not to use *sh and switch to a different language instead. On the other hand, using this in scrips to append entries to some PATH-like variables, it will run with string constants and not user input...
    – user62916
    Apr 2, 2014 at 4:31
  • 1
    you can do eval safely - but it takes a lot of thought. If you're just trying to reference a parameter, you'd wanna do something like this: eval "$1=\"\$2\"" that way on eval's first pass it only evaluates $1 and on the second it does value="$2". But you need to do something else - this is unnecessary here.
    – mikeserv
    Apr 2, 2014 at 5:20
  • Actually, my above comment is wrong, too. You've gotta do "${1##*[;"$IFS"]}=\"\$2\"" - and even that comes with no warranty. Or eval "$(set -- $1 ; shift $(($#-1)) ; echo $1)=\"\$2\"". It's not easy.
    – mikeserv
    Apr 2, 2014 at 9:51

Named arguments simply aren't how Bash's syntax was designed. Bash was designed to be an iterative improvement upon the Bourne shell. As such it needs to ensure certain things work between the two shells as much as possible. So it isn't meant to be easier to script with overall, it's just meant to be better than Bourne while ensuring that if you take a script from a Bourne environment over to bash it's as easy as possible. That isn't trivial since a lot of shells still treat Bourne as a de facto standard. Since people write their scripts to be Bourne-compatible (for this portability) the need remains in force and is unlikely to ever change.

You're probably better off looking at a different shell script (like python or something) entirely if it's at all feasible. If you're running up against a language's limitations, you need to start using a new language.

  • Maybe early in bash's life this was true. But now specific provisions are made. Full reference variables are now available in bash 4.3 - see derobert's answer.
    – Graeme
    Apr 2, 2014 at 1:38
  • And if you look here, you'll see you can do this kind of thing really easily even with just POSIX portable code: unix.stackexchange.com/a/120531/52934
    – mikeserv
    Apr 2, 2014 at 5:27

With standard sh syntax (would work in bash, and not only in bash), you could do:

assign() {
  eval '
    case :${'"$1"'}: in
      (::) '"$1"'=$2;;   # was empty, copy
      (*:"$2":*) ;;      # already there, do nothing
      (*) '"$1"'=$1:$2;; # otherwise, append with a :

Like for solutions using bash's declare, it's safe as long as $1 contains a valid variable name.



This is very simply done and bash is not required at all - this is the basic POSIX specified behavior of assignment via parameter expansion:

: ${PATH:=this is only assigned to \$PATH if \$PATH is null or unset}

To demo in a similar vein to @Graeme, but in a portable way:

_fn() { echo "$1 ${2:-"$1"} $str" ; }

% str= ; _fn "${str:=hello}"
> hello hello hello

And there I only do str= to ensure it has a null value, because parameter expansion has the built-in safeguard against reassigning shell environment inline if it's already set.


For your specific problem, I don't believe named arguments are necessary, though they're certainly possible. Use $IFS instead:

assign() { oFS=$IFS ; IFS=: ; add=$* 
    set -- $PATH ; for p in $add ; do { 
        for d ; do [ -z "${d%"$p"}" ] && break 
        done ; } || set -- $* $p ; done
    PATH= ; echo "${PATH:="$*"}" ; IFS=$oFS

Here's what I get when I run it:

% PATH=/usr/bin:/usr/yes/bin
% assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/nope/bin \
    /usr/bin \
    /nope/usr/bin \

> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin

% echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin

% dir="/some crazy/dir"
% p=`assign /usr/bin /usr/bin/new "$dir"`
% echo "$p" ; echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin:/some crazy/dir:/usr/bin/new
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin:/some crazy/dir:/usr/bin/new

Notice it only added the arguments that weren't already in $PATH or that came before? Or even that it took more than one argument at all? $IFS is handy.

  • hi, I failed to follow, so could you please elaborate it a little bit more? thanks.
    – ramgorur
    Apr 1, 2014 at 22:12
  • I'm already doing so... Just a few more moments please...
    – mikeserv
    Apr 1, 2014 at 22:13
  • @ramgorur Any better? Sorry, but real life intruded and it took me a little longer than I expected to finish the write-up.
    – mikeserv
    Apr 2, 2014 at 0:58
  • same here as well, succumbed to real life. Looks like lots of different approaches to code this thing, let me give me sometime to settle on the best one.
    – ramgorur
    Apr 2, 2014 at 4:52
  • @ramgorur - of course, just wanted to make sure I didn't leave you hanging. About this - you choose whatever you want, man. I will say none of the other answers I can see offer as concise, portable, or as robust a solution as assign does here. If you have questions about how it works, I'd be happy to answer. And by the way, if you really want named arguments, you might wanna look at this other answer of mine in which I show how to declare a function named for another function's arguments: unix.stackexchange.com/a/120531/52934
    – mikeserv
    Apr 2, 2014 at 5:10

I can't find anything like ruby, python, etc. but this feels closer to me

foo() {
  BAR="$1"; BAZ="$2"; QUUX="$3"; CORGE="$4"

Readability is better in my opinion, 4 lines is excessive for declaring your parameter names. Looks closer to modern languages as well.

  • (1) The question is about shell functions.  Your answer presents a skeleton shell function.  Beyond that, your answer has nothing to do with the question.  (2) You think it increases readability to take separate lines and concatenate them into one line, with semicolons as separators?  I believe that you’ve got it backwards; that your one-line style is less readable than the multi-line style. Jul 12, 2019 at 19:29
  • If reducing unnecessary parts is the point, then why the quotes and semicolons? a=$1 b=$2 ... works just as well.
    – ilkkachu
    Jul 12, 2019 at 20:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .