如何使用设置全局变量的函数的输出填充数组?

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

我有一个 shell 脚本,它调用一个函数,该函数根据全局变量的值表现不同,其输出是我想存储在数组中的值列表。

我 运行 遇到了问题,因为当我尝试使用明显语法的任何变体捕获函数的输出时:

mapfile -i the_array < <( the_function )

我在 the_function 中设置的全局变量会在 the_function returns 后恢复到之前的值。我知道这是一个已知的 "feature" 捕获具有副作用的函数的输出,我可以如下所示解决它,但我想知道:

  1. 进入 bash 使此解决方法成为必要的基本原理是什么?
  2. 这真的是解决问题的最佳方法吗?

为了简化问题,考虑这种情况,我希望函数在第一次调用时打印 5 个数字,而在下次调用时不打印任何东西(这是明显的语法,不会产生预期的输出):

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

由于上述原因,这不起作用,我可以通过编写代码来解决这个问题(这个确实产生了预期的输出):

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

the_function() {
    local arr_ref=

    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=()

但是虽然它有效,但它显然是可怕的代码,因为它要求较低级别的原语比必要的更复杂,并且与用于存储它的输出的数据结构紧密耦合(因此如果出现我们只是例如,希望 5 个数字转到标准输出)。

那么 - 为什么我需要这样做,还有更好的方法吗?

如果您不想并行化(因此子 shell 的范围会丢失),替代方法是缓冲。 Bash 不为您执行此操作会使存储空间的使用变得明确可见,并且您的数据存储在何处。所以:

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

要使这种模式自动化,我建议如下:

call_and_store_output() {
  local varname tempfile retval

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

...此后:

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