Leonid99
Leonid99

Reputation: 1297

Declaring global variable inside a function

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.

Upvotes: 32

Views: 53682

Answers (11)

Deepak Mohanty
Deepak Mohanty

Reputation: 121

-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 "$@"

Upvotes: 0

Oneon
Oneon

Reputation: 51

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.

Upvotes: 5

tomy
tomy

Reputation: 464

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

Upvotes: 3

Zach
Zach

Reputation: 148

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.

Upvotes: 4

Ekeyme Mo
Ekeyme Mo

Reputation: 1295

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_[@]}"\)
}

Upvotes: 0

gniourf_gniourf
gniourf_gniourf

Reputation: 46903

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.

Upvotes: 10

ASG
ASG

Reputation: 13

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[@]}

Upvotes: 0

kipibenkipod
kipibenkipod

Reputation: 191

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.

Upvotes: 19

Beorn Harris
Beorn Harris

Reputation: 115

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 "\"'"
"'

Upvotes: 5

minopret
minopret

Reputation: 4806

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

Upvotes: 4

William Pursell
William Pursell

Reputation: 212634

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

Upvotes: 8

Related Questions