我可以轻松地从函数调用(在子 shell 中)捕获 stdout 到变量:
val="$(get_value)"
我还可以在 shell 中修改变量(例如数组)引用可以说,在同一个 shell 中,类似于:
function array.delete_by_index {
local array_name="$1"
local key="$2"
unset "$array_name[$key]"
eval "$array_name=(\"\${$array_name[@]}\")"
}
array.delete_by_index "array1" 0
但我正在努力弄清楚如何以干净的方式同时完成这两件事。我想要的一个例子是从数组中弹出一个值:
function array.pop {
local array_name="$1"
local last_index=$(( $(eval "echo \${#$array_name[@]}") - 1 ))
local tmp="$array_name[\"$last_index\"]"
echo "${!tmp}"
# Changes "$array_name" here, but not in caller since this is a sub shell
array.delete_by_index "$array_name" $last_index
}
val="$(array.pop "array1")"
在我看来,将 stdout 捕获到变量的所有形式都需要 bash 中的子 shell,并且使用子 shell 不允许我在调用者的上下文中通过引用更改值。
我想知道是否有人知道一种神奇的 bashism 来实现这一点?我并不特别想要在文件系统上使用任何类型的文件/fifo 的解决方案。
第二个答案在这个问题中似乎表明这在ksh
using中是可能的val="${ cmd; }"
,因为这种构造显然允许捕获输出,但不使用子 shell。所以是的,从技术上讲我可以切换到 ksh,但我想知道这在 bash 中是否可行。
答案1
这适用于bash
(自版本 4.3 以来)和ksh93
.要“bashify”它,请将函数中的all 替换typeset
为,将全局范围内的 替换为(同时保留所有选项!)。老实说,我不知道为什么 Bash 对那些只是.local
typeset
declare
typeset
function stack_push
{
typeset -n _stack="$1"
typeset element="$2"
_stack+=("$element")
}
function stack_pop
{
typeset -n _stack="$1"
typeset -n _retvar="$2"
_retvar="${_stack[-1]}"
unset _stack[-1]
}
typeset -a stack=()
stack_push stack "hello"
stack_push stack "world"
stack_pop stack value
printf '%s ' "$value"
stack_pop stack value
printf '%s\n' "$value"
在函数中使用 nameref 可以避免eval
(我从来没有eval
在任何脚本中的任何地方使用过!)。通过为stack_pop
函数提供存储弹出值的位置,您可以避免使用子 shell。通过避开子shell,函数可以修改外部作用域中变量stack_pop
的值。stack
函数中局部变量中的下划线是为了避免 nameref 与其引用的变量同名(Bash 不喜欢它,ksh
不介意,请参阅这个问题)。
你可以这样ksh
写函数stack_pop
function stack_pop
{
typeset -n _stack="$1"
printf '%s' "${_stack[-1]}"
unset _stack[-1]
}
然后调用它
printf '%s %s\n' "${ stack_pop stack }" "${ stack_pop stack }"
(${ ... }
与相同$( ... )
但不创建子shell)
但我不太喜欢这个。恕我直言,stack_pop
不必将数据发送到标准输出,并且我不必调用它来${ ... }
获取数据。我可能更喜欢我原来的stack_pop
,然后添加一个stack_pop_print
执行上述操作的,如果需要的话。
stack_pop
对于 Bash,您可以在我的文章开头使用,然后使用stack_top_print
,将堆栈的顶部元素打印到标准输出,而不删除它(它不能删除它,因为它很可能在$( ... )
子 shell中运行) )。
答案2
我能想到的不使用文件或修改函数的唯一解决方案是运行命令两次,其中一个在子 shell 中运行以进行捕获,然后另一个在父 shell 中修改变量。
val="$(array.pop "array1")" 数组.pop 数组1
如果可以选择重写该函数:
函数 array.pop { 本地数组名称=“$1”; 本地last_index=$(( $(eval "echo \${#$array_name[@]}") - 1 )); 本地 tmp="$array_name[\"$last_index\"]"; 回声“${!tmp}”; 如果 [[ $2 ]];然后 eval "$2=\"${!tmp}\""; # 这里有魔法攻击 菲 array.delete_by_index "$array_name" "$last_index"; }
运行为array.pop 'array1' variablename
,它也会初始化变量,即使它不存在:
$ array1=(一二三) $ array.pop array1 var 三 $回显“$var” 三 $ echo "${array1[*]}" 一二