我有一个shell脚本,它根据全局变量的值调用一个行为不同的函数,其输出是我想要存储在数组中的值列表。
我遇到了一个问题,因为当我尝试使用明显语法的任何变体来捕获函数的输出时:
mapfile -i the_array < <( the_function )
我在the_function
内设置的全局变量会在the_function
返回后恢复为之前的值。我知道这是捕获具有副作用的函数输出的已知“特征”,我可以解决它,如下所示,但我想知道:
为了简化问题,请考虑这种情况,我希望函数在第一次调用时打印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。
那么 - 为什么我需要这样做并且有更好的方法吗?
答案 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"