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

时间:2018-02-15 14:57:19

标签: arrays bash pass-by-reference side-effects

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

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

mapfile -i the_array < <( the_function )

我在the_function内设置的全局变量会在the_function返回后恢复为之前的值。我知道这是捕获具有副作用的函数输出的已知“特征”,我可以解决它,如下所示,但我想知道:

  1. 使用这种解决方法进行bash的理由是什么?
  2. 这真的是解决问题的最好办法吗?
  3. 为了简化问题,请考虑这种情况,我希望函数在第一次调用时打印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=$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=()
    

    虽然这有效但它显然是可怕的代码,因为它要求较低级别的原语比必要的更复杂并且紧密耦合到用于存储它的输出的数据结构(因此如果出现我们想要的情况,则不可重复使用例如,5个数字去stdout。

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

1 个答案:

答案 0 :(得分:3)

如果您不想进行并行化(因此也不想要其范围丢失的子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=$1 || 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"