需要捕获 stdout 并通过 bash 中函数的引用修改变量

需要捕获 stdout 并通过 bash 中函数的引用修改变量

我可以轻松地从函数调用(在子 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 的解决方案。

第二个答案在这个问题中似乎表明这在kshusing中是可能的val="${ cmd; }",因为这种构造显然允许捕获输出,但不使用子 shell。所以是的,从技术上讲我可以切换到 ksh,但我想知道这在 bash 中是否可行。

答案1

这适用于bash(自版本 4.3 以来)和ksh93.要“bashify”它,请将函数中的all 替换typeset为,将全局范围内的 替换为(同时保留所有选项!)。老实说,我不知道为什么 Bash 对那些只是.localtypesetdeclaretypeset

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[*]}"
一二

相关内容