Necesita capturar stdout y modificar la variable por reference de la function en bash

Puedo capturar fácilmente stdout de una llamada de function (en subcaja) a una variable con:

val="$(get_value)" 

También puedo modificar variables (por ejemplo, una matriz) en un shell por reference, por así decirlo, en el mismo shell con algo como:

 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 

Pero lo que estoy luchando para encontrar la manera de hacerlo es hacer ambas cosas al mismo time, de una manera limpia. Un ejemplo de dónde quiero esto es mostrar un valor de una matriz:

 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")" 

Me parece que todas las forms de capturar stdout para una variable requieren una subshell in bash, y usar un subcarte no me permitirá cambiar un valor por reference en el context de la persona que llama.

Me pregunto si alguien sabe un bashism mágico para lograr esto? Particularmente no quiero una solución que use cualquier tipo de file / fifo en el sistema de files.

La segunda respuesta en esta pregunta parece sugerir que esto es posible en ksh usando val="${ cmd; }" , ya que aparentemente este constructo permite capturar resultados, pero sin usar subsets. Así que sí, técnicamente podría cambiar a ksh, pero me gustaría saber si esto es posible en bash.

Esto funciona tanto en bash (desde la versión 4.3) como en ksh93 . Para "bashificarlo", reemplace todo tipo de typeset con local en las funciones, y el tipo de typeset en el scope global con declare (¡manteniendo todas las opciones!). Honestamente, no sé por qué Bash tiene tantos nombres diferentes para las cosas que son solo variaciones de 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" 

Utilizando un nombreref en la function, evitas la eval (¡nunca he tenido que usar eval en ninguna parte de ningún script!). Al proporcionar la function stack_pop con un lugar para almacenar el valor reventado, se evita la subshell. Al evitar la subshell, la function stack_pop puede modificar el valor de la variable de stack en el ámbito externo.

El subrayado en las variables locales de la function es evitar tener un nombreref que tenga el mismo nombre que la variable a la que hace reference (a Bash no le gusta, a ksh no le molesta, vea esta pregunta ).

En ksh puedes escribir la function stack_pop como

 function stack_pop { typeset -n _stack="$1" printf '%s' "${_stack[-1]}" unset _stack[-1] } 

Y luego llámalo con

 printf '%s %s\n' "${ stack_pop stack }" "${ stack_pop stack }" 

( ${ ... } es lo mismo que $( ... ) pero no crea una subcadena)

Pero no soy un gran admirador de esto. En mi humilde opinión, stack_pop no debería tener que enviar los datos a stdout, y no debería tener que llamarlo con ${ ... } para get los datos. Posiblemente podría estar más bien con mi stack_pop original, y luego agregar un stack_pop_print que haga lo anterior, si es necesario.

Para Bash, podrías ir con el stack_pop al principio de mi publicación, y luego tener un stack_top_print que simplemente imprime el elemento superior de la stack en stdout, sin eliminarlo (lo cual no es posible porque probablemente se esté ejecutando en una $( ... ) subshell).

La única solución en la que podía pensar sin utilizar files o modificar la function era ejecutar el command dos veces, en el que uno se ejecutaba en una subescama para capturar y luego el otro modificaba la variable en el shell primario.

 val = "$ (array.pop" array1 ")"
 array.pop array1

Si reescribir la function es una opción:

 function array.pop {
     local array_name = "$ 1";
     local last_index = $ (($ (eval "echo \ $ {# $ array_name [@]}") - 1));
     tmp local "$ nombre_de_la_arreglo [\" $ last_index \ "]";
     echo "$ {! tmp}"; 

     si [[$ 2]];  entonces
         eval "$ 2 = \" $ {! tmp} \ "";  # magia bashism aquí 
     fi

     array.delete_by_index "$ array_name" "$ last_index"; 
 }

Para ejecutar como array.pop 'array1' variablename , que también inicializará la variable, incluso si no existe:

 $ array1 = (uno dos tres)
 $ array.pop array1 var
 Tres
 $ echo "$ var"
 Tres
 $ echo "$ {array1 [*]}"
 uno dos