33

What I want to do is following. Inside a function, I need to assign a value to a variable, whose name is taken from another variable. In other words:

func() { # # Here happens something that ultimately makes $arg="var_name" # declare -g ${arg}=5 } func echo ${var_name}; # Prints out "5" 

The code snippet above works great in bash 4.2. However, in bash before 4.2, declare doesn't have the -g option. Everything I found at google says that to define the global variable inside a function, I should just use the var=value syntax, but unfortunately var itself depends on another variable. ${arg}=5 doesn't work, either. (It says -bash: var_name=5: command not found.

For the curious, the reason for all this is that this function actually creates global variables from the script parameters, i.e. running script --arg1=val automatically creates variable named arg1 with value val. Saves tons of a boilerplate code.

3
  • 1
    You could construct your var=value as a string and evaluate it using the bash builtin command eval. Commented Mar 26, 2012 at 12:31
  • @minopret Can you repost your comment as an answer? Unless someone comes up with a less hackish solution, I will just accept your answer, it definitely works. Commented Mar 26, 2012 at 12:51
  • > running script --arg1=val automatically creates variable named arg1 with value val ermmm... can't you just run this like arg1=val script and it will be set for the duration of running the script? Commented Dec 18, 2021 at 18:34

11 Answers 11

19

declare inside a function doesn't work as expected. I needed read-only global variables declared in a function. I tried this inside a function but it didn't work:

declare -r MY_VAR=1 

But this didn't work. Using the readonly command did:

func() { readonly MY_VAR=1 } func echo $MY_VAR MY_VAR=2 

This will print 1 and give the error "MY_VAR: readonly variable" for the second assignment.

Sign up to request clarification or add additional context in comments.

1 Comment

Using declare -rg should work as well. From the docs: "When used in a function, 'declare' makes NAMEs local, as with the 'local' command. The '-g' option suppresses this behavior."
10

I think you need the printf builtin with its -v switch:

func() { printf -v "$var" '5' } var=var_name func echo "$var_name" 

will output 5.

Comments

8

Since this is tagged shell, it is important to note that declare is a valid keyword only in a limited set of shells, so whether it supports -g is moot. To do this sort of thing in a generic Bourne shell, you can just use eval:

eval ${arg}=5 

Comments

6

The existence of the local and declare builtin commands may render it counter-intuitive that the wanted functionality is actually available from the export builtin, at least on Bash.

According to the Bash manual, the export command has an -n option, which "causes the export property to be removed from each name". This also applies while declaring variables with values right ahead.

The following snippet demonstrates the usage:

VAR=old-value function func () { export -n "$1=$2" } func VAR new-value echo "VAR=$VAR" # prints VAR=new-value 

One may create one own's global function, and use it inside other functions:

VAR=old-value function global () { export -n "$1=$2" } function main () { global VAR "$1" } main main-value echo "VAR=$VAR" # prints VAR=main-value 

This is verified working on

  • an old Bash 2.03.0(1) running on a (modern) OS/390 z/OS
  • on a Bash 3.2.57(1) on macOS 10.15
  • on a Bash 4.1.2(2) on CentOS 6

I used this to inspect the various possibilities:

#!env bash export LANG=C LC_ALL=C A=a0 B=b0 C=c0 D=d0 E=e0 export F=f0 function testfunc () { local "$1=$2" ; shift 2 declare "$1=$2" ; shift 2 declare -g "$1=$2" ; shift 2 export -n "$1=$2" ; shift 2 export "$1=$2" } echo "$A/$B/$C/$D/$E" testfunc A a1 B b1 C c1 D d1 E e1 echo "$A/$B/$C/$D/$E" export -p | grep -E '^declare -x [ABCDEF]=' 

In case of macOS 10.15 this shows an output similar to this:

$ bash test.sh a0/b0/c0/d0/e0 test.sh: line 15: declare: -g: invalid option declare: usage: declare [-afFirtx] [-p] [name[=value] ...] a0/b0/c0/d1/e1 declare -x E="e1" declare -x F="f0" 

The output shows that the local and declare commands used in the testfunc function effectively do not apply the variables in the global context (as documented) and that the declare -g option is not widely supported yet. It also shows that the variables D and E have been changed in the global context from inside the testfunc function (as hoped for), while only the E variable has been marked for export as a side effect by the export command, showing that the export -n option is effective. The variable F is there just to verify the grep command worked as expected.

Comments

5

Using "eval" (or any direct assignment) can give you headaches with special characters (or issues with value injection etc).

I've had great success just using "read"

$ function assign_global() { > local arg="$1" > IFS="" read -d "" $arg <<<"$2" >} $ assign_global MYVAR 23 echo $MYVAR 23 $ assign_global MYVAR "\"'" "' 

3 Comments

But this appends a trailing newline to the variable…
@gniourf_gniourf - The given example will indeed append newline, but the example specifically addresses special characters (such as newlines) in the string being read. If you are certain there will be no newlines etc in the argument being read, you can remove the -d option from read to read until, but not including, the first new line character.
I think this will work: read -d"\0" $arg <<< "$2", with no need to change IFS
4

You could construct your var=value as a string and evaluate it using the bash builtin command eval.

2 Comments

@sivann look at my answer
eval var=\"value with a space\" if you need to use quotes, they must be escaped
4

If you want something a little less hackish, try using export in place of declare -g. It has the added benefit of being an environment variable now.

func() { # # Here happens something that ultimately makes $arg="var_name" # export ${arg}=5 } func echo ${var_name}; # Prints out "5" 

Unfortunately, this still does not work for arrays.

1 Comment

to work with arrays export will not work, you can look at my answer
3

In Bash for declaring array variables inside a function definition you can use the local command in combination with the eval command like in the following example:

#!/bin/bash function test { local -g $1 local array=( AA BB 'C C' DD) eval ${1}='("${array[@]}")' } test VAR echo VAR=${VAR[@]:1:2} 

the output will be:

$./test.sh VAR=BB C C 

works with: GNU bash, Version 4.4.18

2 Comments

works with GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18) too
this takes advantage of the fact that local is a synonym for declare, but using local -g seems strange, as you're doing the opposite of local. I would use declare -g just to make it clear what's going on. From the docs: "When used in a function, 'declare' makes NAMEs local, as with the 'local' command. The '-g' option suppresses this behavior."
0

Eval will work with Array variables...I just had to single quote the local Array variable inside the eval statement.

The below reads in a file (1st arg) a line at a time and then copies it to the variable name passed to the 2nd arg of the get_file.

get_file () { declare -a Array=(); while read line; do Array=("${Array[@]}" "($line)") done < ${1} eval ${2}='("${Array[@]}")' unset Array } declare -a my_array=(); get_file /etc/myfile.conf my_array echo ${#my_array[@]}

4 Comments

Are you trying to implement the mapfile builtin? mapfile -t my_array < /etc/myfile.conf will gladly do that for you, and more efficiently too.
Unfortunately this is for a Solaris 10 application which doesn't appear to include mapfile in that bash implementation. So hence the kludge
mapfile is a Bash builtin, so OS is irrelevant. Bash version is relevant, though. mapfile appears in version 4 (which is already a few years old).
That would explain it. The bash version on the system i'm using to test is an unpatched version 3.2.
0

Maybe your could use the following functions to dynamically assign your global variable in bash(< 4.2). If > 2 args passed, then the value will be array type. E.G

_set2globals variable_name arg1 [arg2] [arg3] [...] 

Source:

# dynamically set $variable_name($1)=$values($2...) to globals scope function _set2globals() { if (( $# < 2 )); then printf "$FUNCNAME: expect at least 2 argument, but %d you given.\n" $# >&2 exit 1 fi local ___pattern_='^[_a-zA-Z][_0-9a-zA-Z]*$' if [[ ! $1 =~ $___pattern_ ]]; then printf "$FUNCNAME: invalid variable name: %s.\n" "$1" >&2 exit 1 fi local __variable__name__=$1 shift local ___v_ local ___values_=() while (($# > 0)); do ___v_=\'${1//"'"/"'\''"}\' ___values_=("${___values_[@]}" "$___v_") # push to array shift done eval $__variable__name__=\("${___values_[@]}"\) } 

Comments

0

-g -r does the job. My bash version is: 5.1.16. Here is an example:

function setup() { declare -g -r VERSION="${1:-2.0}" } function main() { setup "$@" VERSION="2.3" # error } main "$@" 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.