1

I want to be able to call a function, and keep all the modifications that the function will perform on the string.

For example: I tried this script

#!/bin/bash

change_string() {
  var=$1
  var+=",Hello2"
  echo "Function: $var"
}

myString="Hello1"
change_string $myString

echo "Main: $myString"

This will print out:

Function: Hello1,Hello2
Main: Hello1

Is there any way to change the $myString inside function call, but keep those changes in main? I think I probably missed something in bash related to pass-by-reference?

2
  • BTW, this smells a bit like a use case where you should probably be using an array rather than a string in the first place. Commented Nov 8, 2017 at 22:59
  • I'm using this for clang-offload-bundler which consumes an option with command delimited args. Thanks for the help! Commented Nov 8, 2017 at 23:21

3 Answers 3

4

With Bash 4.3+ (namerefs!)

Modern versions of bash provide "nameref" support, which allows a variable to refer to another.

#!/usr/bin/env bash

case $BASH_VERSION in
  [123].*|4.[012].*) echo "This needs bash 4.3 or newer" >&2; exit 1;;
esac

# local variable is prefixed because this will fail if we're passed a variable name we use
# internally; prefixing the local names makes such collisions unlikely.
change_string() {

  # make change_string__var an alias for the variable named in our argument
  declare -n change_string__var=$1

  # append to that variable
  change_string__var+=",Hello2"

  # ...and log the new value, 'cuz that's what the original code did.
  echo "Function: $change_string__var"
}

myString="Hello1"
change_string myString  ## pass variable **name**, not variable value
echo "Main: $myString"

With Bash 3.x+ (indirect evaluation + indirect assignment)

Alternately, as a less-newfangled approach, one can use ${!var} to do an indirect reference, and printf -v to do an indirect assignment.

#!/usr/bin/env bash

change_string() {

  # Store the variable name in a regular local variable
  local change_string__var=$1

  # Use ${!var} to get the value of the variable thus named
  local change_string__val=${!change_string__var}

  # Use ''printf -v varname ...'' to assign a new value
  printf -v "$change_string__var" %s "${change_string__val},Hello2"
}

myString="Hello1"
change_string myString
echo "Main: $myString"
Sign up to request clarification or add additional context in comments.

3 Comments

Cool, but now won't trust functions with my variables!
Dirty use of printf :)
I think the namerefs is exactly what I needed. Thanks for giving me more than 1 bash versions.
1

You have to pass the name of the variable to your function, not its value. Once you do that, there are a couple of options.

The simplest is probably to use a nameref (which requires bash 4.3 or later):

change_string() {
  declare -n var=$1
  var+=",Hello2"
}

$ foo="Hello1"
$ change_string foo
$ echo "$foo"
Hello1,Hello2

Before namerefs were introduced, you could use the declare -g, which required bash 4.2 or later:

change_string () {
  var=$1
  old_val=${!var}
  declare -g "$1=${old_val},Hello2"
}

Prior to 4.2, you were pretty much stuck using eval and crossing your fingers that you didn't get bitten by a code injection attack.

change_string () {
  var=$1
  eval "$1=\$$1,Hello2"
}

None of the approaches are entirely fool-proof, though; see this section of Bash FAQ 048. My advice is to try to avoid needing such a function.

1 Comment

And as usual, I forgot about printf -v as pointed as by Charles Duffy.
0

Bash is one of the rare languages that has dynamic scoping (unlike the usual lexical scoping). That means that functions are accessing variables from the last scope they were defined in (that might be an assignment or a declaration with declare or local).

For example:

#!/bin/bash
fun() {
    x="newval"
}

x=start
echo "$x"
fun
echo "$x"

Will print:

start
newval

This simple example looks like x is a simple global variable, but it's more than that (scopes are dynamically nested, always the last/previous scope is accessed).

So, if you want to change the variable in your function, just change it.

#!/bin/bash
change_string() {
    myString+=",Hello2"
}

myString="Hello1"
change_string $myString

echo "Main: $myString"

You can even change the variable "by reference", if you pass the name of the variable, then use declare -g to set (in outer scope) the value of a variable whose name we know (varname) by appending a text to the current value (accessed via indirect expansion):

change_string() {
    varname="$1"
    declare -g "$varname"="${!varname},Hello2"
}

myString="Hello1"
change_string myString

# myString is now "Hello1,Hello2"

3 Comments

In your point about changing your variable by just changing it, how did this work? Since most others are suggesting to use namerefs
If you know the name of the variable, you can change it directly. If you want to pass the name of variable, and then change the referred variable, you can use declare -g or namerefs via declare -n.
Okay, thanks that makes sense, I guess it just depends on the usage.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.