【问题标题】:How do I populate an array with the output of a function that sets a global variable?如何使用设置全局变量的函数的输出填充数组?
【发布时间】:2018-07-26 09:08:16
【问题描述】:

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

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

mapfile -i the_array < <( the_function )

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

  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=$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 个数字去标准输出,例如)。

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

【问题讨论】:

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


    【解决方案1】:

    如果您不想要并行化(以及因此丢失范围的子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"
    

    【讨论】:

    • 谢谢查尔斯。你知道为什么x=$(y) 导致y 在并行子shell 中运行而只是单独调用y 而没有在变量中捕获它的输出的原因是什么显然不这样做?真的没有办法只运行y 并在没有子shell 的变量中捕获它的输出吗?在当前 shell 中运行 y 的某种 source y. y 等效语法?
    • @EdMorton, ...如果x=$(y)没有分叉,你会如何实现它?现在,您只有一个 FIFO 作为内部的标准输出,而外部进程只负责从该 FIFO 读取,直到它到达 EOF。否则,您需要维护两组不同的流控制/状态,并且每当在内部轨道上运行外部命令时都需要切换到类似于 FIFO 机制的东西。请记住,POSIX sh 的设计时间很长,long 在 pthreads 之前。
    • 我希望它的实现方式与单独调用函数 y 的实现方式或通过 . script 调用 shell 脚本的方式相同,但我不太熟悉shell实现的胆量。我真的只是在寻找一种简洁、一致的方式来调用函数并将其输出保存在变量(在本例中为数组)中,而不管该函数内部的代码做什么。到目前为止,没有一个选项(实现临时文件或将函数与调用者紧密耦合)很好,所以我希望 something 其他我可以做。
    • “实现一个临时文件”很容易推入一个函数;编辑演示。
    • @EdMorton,...x=$(somescript) 所拥有的而 . script 所没有的东西是需要捕获和存储所有标准输出内容。实际上需要从该FIFO中执行read()。要进行该调用,它需要 running -- 它必须有一个活动的执行线程。因此,除非我们将内容分流到其他地方以推迟读取,否则单独的过程是不可避免的。
    猜你喜欢
    • 2023-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-19
    • 2021-10-27
    • 2014-04-01
    • 2013-10-21
    相关资源
    最近更新 更多