如何使用设置全局变量的函数的输出填充数组?
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" 捕获具有副作用的函数的输出,我可以如下所示解决它,但我想知道:
- 进入 bash 使此解决方法成为必要的基本原理是什么?
- 这真的是解决问题的最佳方法吗?
为了简化问题,考虑这种情况,我希望函数在第一次调用时打印 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"
我有一个 shell 脚本,它调用一个函数,该函数根据全局变量的值表现不同,其输出是我想存储在数组中的值列表。
我 运行 遇到了问题,因为当我尝试使用明显语法的任何变体捕获函数的输出时:
mapfile -i the_array < <( the_function )
我在 the_function
中设置的全局变量会在 the_function
returns 后恢复到之前的值。我知道这是一个已知的 "feature" 捕获具有副作用的函数的输出,我可以如下所示解决它,但我想知道:
- 进入 bash 使此解决方法成为必要的基本原理是什么?
- 这真的是解决问题的最佳方法吗?
为了简化问题,考虑这种情况,我希望函数在第一次调用时打印 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"