How do I populate an array with the output of a function that sets a global variable?

Ed Morton

I have a shell script which calls a function that behaves differently based on the value of a global variable and whose output is a list of values that I want to store in an array.

I'm running into a problem because when I try to capture the output of the function using any variation of the obvious syntax:

mapfile -i the_array < <( the_function )

my global variable that's being set inside the_function reverts to it's previous value once the_function returns. I understand that this is a known "feature" of capturing the output of a function that has side-effects and I can work around it as shown below but I'd like to know:

  1. What's the rationale that went into bash to make this workaround necessary?
  2. Is this really the best way to get around the problem?

To simplify the problem, consider this case where I want the function to print 5 numbers the first time it's called and not print anything the next time it's called (this is the obvious syntax which doesn't produce the expected output):

$ cat tst1
#!/usr/bin/env bash

the_function() {
    printf '\nENTER: %s(), the_variable=%d\n' "${FUNCNAME[0]}" "$the_variable" >&2

    if (( the_variable == 0 )); then
        seq 5
        the_variable=1
    fi

    printf 'EXIT: %s(), the_variable=%d\n' "${FUNCNAME[0]}" "$the_variable" >&2
}

the_variable=0

mapfile -t arr < <( the_function )
declare -p arr

mapfile -t arr < <( the_function )
declare -p arr

$ ./tst1

ENTER: the_function(), the_variable=0
EXIT: the_function(), the_variable=1
declare -a arr=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5")

ENTER: the_function(), the_variable=0
EXIT: the_function(), the_variable=1
declare -a arr=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5")

That doesn't work for the reasons stated above and I can work around it by writing the code as (this one does produce the expected output):

$ cat tst2
#!/usr/bin/env bash

the_function() {
    local arr_ref=$1

    printf '\nENTER: %s(), the_variable=%d\n' "${FUNCNAME[0]}" "$the_variable" >&2

    if (( the_variable == 0 )); then
        mapfile -t "$arr_ref" < <( seq 5 )
        the_variable=1
    else
        mapfile -t "$arr_ref" < /dev/null
    fi

    printf 'EXIT: %s(), the_variable=%d\n' "${FUNCNAME[0]}" "$the_variable" >&2
}

the_variable=0

the_function arr
declare -p arr

the_function arr
declare -p arr

$ ./tst2

ENTER: the_function(), the_variable=0
EXIT: the_function(), the_variable=1
declare -a arr=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5")

ENTER: the_function(), the_variable=1
EXIT: the_function(), the_variable=1
declare -a arr=()

but while that works it's obviously horrible code since it requires the lower level primitive to be more complicated than necessary and tightly coupled to the data structure being used to store it's output (so not reusable if a case arises where we just want the 5 numbers to go to stdout, for example).

So - why do I need to do that and is there a better way?

Charles Duffy

If you don't want parallelization (and thus a subshell whose scope gets lost), the alternative is buffering. Bash not doing that for you makes it explicit and visible that storage is being used, and where your data gets stored. So:

tempfile=$(mktemp "${TMPDIR:-/tmp}/the_function_output.XXXXXX")
the_function >"$tempfile"
mapfile -i the_array < "$tempfile"
rm -f -- "$tempfile"

To automate this kind of pattern, I'd suggest something like:

call_and_store_output() {
  local varname tempfile retval

  varname=$1 || return; shift
  tempfile=$(mktemp "${TMPDIR:-/tmp}/cso.XXXXXX") || return
  "$@" >"$tempfile"
  local retval=$?
  printf -v "$varname" %s "$(<"$tempfile")"
  rm -f -- "$tempfile"
  return "$retval"
}

...thereafter:

call_and_store_output function_output_var the_function
mapfile -i the_array <<<"$function_output_var"

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How do I populate a bash associative array with command output?

How do i store output of the function into an array

How do I get rid of global variable for recursive function in this case?

How do I change the value of a global variable inside of a function

How do I make a variable created inside a function become global?

How do I change the value of any global variable inside of a function?

How do I use an "array" as a Function variable?

In VBA how do I populate an array of type Long and pass to a function to populate cell in excel

How do I assign a variable to a specific function output in python?

How do I set the output of a function as a variable value in a bash script?

PHP OOP - How do I define a CONST with a Variable or Function Output?

How do I populate a bash array with multi-line command output?

How do I populate a column from an array?

How do I populate an array with random numbers?

Excel VBA - How do I populate an array

How do I initialise a const array struct field with the output of a function?

How do I store an output of a function into an array in Kotlin

How do I use a variable from another function without making the variable global in JavaScript?

how can i unset a global variable in function

How could i change a global variable with a function?

How do I initialize a global variable with @MainActor?

How do I store an input as a global variable?

How do I set a global function in Postman?

java - how to avoid global external variable as output in recursive function

How to use output of prompt as global variable in another function

In rust, how do I define a global variable/constant whose value is the result of a function?

How exactly do I write a callback function to change a global variable inside AJAX for jquery?

In OCaml, how do I re-assign a global variable inside a function

How can I get output from method of class into global variable