Captura stdout y stderr en diferentes variables


¿Es posible almacenar o capturar stdout y stderr en diferentes variables, sin usar un archivo temporal? Ahora mismo hago esto para obtener stdout en out y stderr en err al ejecutar some_command, pero como para evitar el archivo temporal.

error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< error_file)
rm $error_file
Author: codeforester, 2012-06-14

13 answers

Ok, se puso un poco feo, pero aquí hay una solución:

unset t_std t_err
eval "$( (echo std; echo err >&2) \
        2> >(readarray -t t_err; typeset -p t_err) \
         > >(readarray -t t_std; typeset -p t_std) )"

Donde (echo std; echo err >&2) necesita ser reemplazado por el comando real. La salida de stdout se guarda en el array $t_std línea por línea omitiendo las nuevas líneas (el -t) y stderr en $t_err.

Si no te gustan los arrays puedes hacer{[13]]}

unset t_std t_err
eval "$( (echo std; echo err >&2 ) \
        2> >(t_err=$(cat); typeset -p t_err) \
         > >(t_std=$(cat); typeset -p t_std) )"

Que prácticamente imita el comportamiento de var=$(cmd) excepto por el valor de $? que nos lleva a la última modificación:

unset t_std t_err t_ret
eval "$( (echo std; echo err >&2; exit 2 ) \
        2> >(t_err=$(cat); typeset -p t_err) \
         > >(t_std=$(cat); typeset -p t_std); t_ret=$?; typeset -p t_ret )"

Aquí $? es preserved into $t_ret

Probado en Debian wheezy usando GNU bash, Versión 4.2.37(1)-release (i486-pc-linux-gnu).

 31
Author: TheConstructor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-22 14:22:39

Jonathan tiene la respuesta. Como referencia, este es el truco de ksh93. (requiere una versión no antigua).

function out {
    echo stdout
    echo stderr >&2
}

x=${ { y=$(out); } 2>&1; }
typeset -p x y # Show the values

Produce

x=stderr
y=stdout

La sintaxis ${ cmds;} es solo una sustitución de comandos que no crea una subcapa. Los comandos se ejecutan en el entorno de shell actual. El espacio al principio es importante ({ es una palabra reservada).

Stderr del grupo de comandos interno se redirige a stdout (de modo que se aplica a la sustitución interna). Siguiente, el stdout de out se asigna a y, y el stderr redirigido es capturado por x, sin la pérdida habitual de y a una subcapa de sustitución de comandos.

No es posible en otros shells, porque todas las construcciones que capturan la salida requieren poner al productor en una subcapa, que en este caso, incluiría la asignación.

actualización: Ahora también soportado por mksh.

 14
Author: ormaaj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:32

Este comando establece los valores stdout (stdval) y stderr (errval) en el shell actual:

eval "$( execcommand 2> >(setval errval) > >(setval stdval); )"

Siempre que se haya definido esta función:

function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }

Cambie execcommand al comando capturado, ya sea "ls", "cp", "df", etc.


Todo esto se basa en la idea de que podríamos convertir todos los valores capturados en una línea de texto con la ayuda de la función setval, luego setval se utiliza para capturar cada valor en esta estructura:

execcommand 2> CaptureErr > CaptureOut

Convertir cada uno captura de valor a una llamada setval:

execcommand 2> >(setval errval) > >(setval stdval)

Envuelva todo dentro de una llamada execute y haga eco de ella:

echo "$( execcommand 2> >(setval errval) > >(setval stdval) )"

Obtendrá las llamadas de declaración que crea cada setval:

declare -- stdval="I'm std"
declare -- errval="I'm err"

Para ejecutar ese código (y obtener el conjunto vars) use eval:

eval "$( execcommand 2> >(setval errval) > >(setval stdval) )"

Y finalmente hacer eco de los vars conjunto:

echo "std out is : |$stdval| std err is : |$errval|

También es posible incluir el valor return (exit).
Un ejemplo completo de script bash se ve así:

#!/bin/bash --

# The only function to declare:
function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }

# a dummy function with some example values:
function dummy { echo "I'm std"; echo "I'm err" >&2; return 34; }

# Running a command to capture all values
#      change execcommand to dummy or any other command to test.
eval "$( dummy 2> >(setval errval) > >(setval stdval); <<<"$?" setval retval; )"

echo "std out is : |$stdval| std err is : |$errval| return val is : |$retval|"
 13
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-09 04:01:56

To sum everything up for the benefit of the reader, here is an

Solución fácil de usar bash

Esta versión utiliza subconchas y se ejecuta sin tempfiles. (Para una versión tempfile que se ejecuta sin subconchas, ver mi otra respuesta.)

: catch STDOUT STDERR cmd args..
catch()
{
eval "$({
__2="$(
  { __1="$("${@:3}")"; } 2>&1;
  ret=$?;
  printf '%q=%q\n' "$1" "$__1" >&2;
  exit $ret
  )"
ret="$?";
printf '%s=%q\n' "$2" "$__2" >&2;
printf '( exit %q )' "$ret" >&2;
} 2>&1 )";
}

Ejemplo de uso:

dummy()
{
echo "$3" >&2
echo "$2" >&1
return "$1"
}

catch stdout stderr dummy 3 $'\ndiffcult\n data \n\n\n' $'\nother\n difficult \n  data  \n\n'

printf 'ret=%q\n' "$?"
printf 'stdout=%q\n' "$stdout"
printf 'stderr=%q\n' "$stderr"

Esto imprime

ret=3
stdout=$'\ndiffcult\n data '
stderr=$'\nother\n difficult \n  data  '

Así que se puede usar sin pensar más en ello. Simplemente pon catch VAR1 VAR2 delante de cualquier command args.. y estás Terminado.

Algunos if cmd args..; then se convertirán en if catch VAR1 VAR2 cmd args..; then. Realmente nada complejo.

Discusión

P: ¿Cómo funciona?

Simplemente envuelve las ideas de las otras respuestas aquí en una función, de tal manera que puede ser fácilmente reutilizada.

catch() básicamente usa eval para establecer las dos variables. Esto es similar a https://stackoverflow.com/a/18086548

Considere una llamada de catch out err dummy 1 2a 3b:

  • Saltemos el eval "$({ y el __2="$(, por ahora. Hablaré de esto más tarde.

  • __1="$("$("${@:3}")"; } 2>&1; ejecuta dummy 1 2 3 y tiendas es stdout en __1 para su uso posterior. Así que __1 se convierte en 2a. También redirige stderr de dummy a stdout, de modo que la captura exterior puede reunirse stdout

  • ret=$?; coge el código de salida, que es 1

  • printf '%q=%q\n' "$1" "$__1" >&2; luego sale out=2a a stderr. stderr aquí se utiliza, como la actual stdout ya ha asumido el papel de stderr de la dummy comando.

  • exit $ret luego reenvía el código de salida (1) a la siguiente etapa.

Ahora hacia el exterior __2="$( ... )":

  • Esto captura stdout de lo anterior, que es el stderr de la llamada dummy, en la variable __2. (Podríamos reutilizar __1 aquí, pero usé __2 para hacerlo menos confuso.). Así que __2 se convierte 3b

  • ret="$?"; captura el código de retorno (devuelto) 1 (de dummy) de nuevo

  • printf '%s=%q\n' "$2" "$__2" >&2; luego sale err=3a a stderr. stderr se usa de nuevo, ya que ya se usó para generar la otra variable out=2a.

  • printf '( exit %q )' "$ret" >&2; then outputs the code to set the proper return value. I did not find a better way, as assignig it to a variable needs a variable name, which then cannot be used as first oder second argument tocatch`.

Tenga en cuenta que, como una optimización, podríamos haber escrito esos 2 printf como uno solo como printf '%s=%q\n( exit %q ) "$__2" "$ret "' también.

Entonces, ¿qué tenemos hasta ahora?

Tenemos lo siguiente escrito a stderr: {[109]]}

out=2a
err=3b
( exit 1 )

De donde out es de $1, 2a es de stdout de dummy, err es de $2, 3b es de stderr de dummy, y 1 es desde el código de retorno de dummy.

Tenga en cuenta que %q en el formato de printf tiene cuidado de citar, de modo que el shell ve argumentos adecuados (individuales) cuando se trata de eval. 2a y 3b son tan simples, que se copian literalmente.

Ahora hacia el exterior eval "$({ ... } 2>&1 )";:

Esto ejecuta todo lo anterior que genera las 2 variables y el exit, lo captura (por lo tanto el 2>&1) y lo analiza en el shell actual usando eval.

De esta manera se establecen las 2 variables y también el código de retorno.

P: Utiliza eval que es malo. Entonces, ¿es seguro?

  • Mientras printf %q no tenga errores, debería ser seguro. Pero siempre tienes que tener mucho cuidado, solo piensa en ShellShock.

P: ¿Errores?

  • No se conocen errores obvios, excepto siguiente:

    • La captura de una salida grande necesita una gran memoria y CPU, ya que todo va en variables y necesita ser analizado de nuevo por el shell. Así que úsalo sabiamente.
    • Como de costumbre $(echo $'\n\n\n\n') traga todos los alimentos de línea, no solo el último. Este es un requisito de POSIX. Si necesita obtener el LFs ileso, simplemente agregue un carácter final a la salida y elimínelo después como en la siguiente receta (mire el final x que permite leer un softlink apuntando a un archivo que termina en un $'\n'):

      target="$(readlink -e "$file")x"
      target="${target%x}"
      
    • Las variables Shell no pueden llevar el byte NUL ($'\0'). Simplemente se ignoran si ocurren en stdout o stderr.

  • El comando dado se ejecuta en un sub-subshell. Por lo tanto, no tiene acceso a $PPID, ni puede alterar las variables del shell. Puede catch una función de shell, incluso builtins, pero esos no serán capaces de alterar las variables de shell (como todo lo que se ejecuta dentro $( .. ) no puede hacer esto). Así que si necesita ejecutar una función en el shell actual y capturar su stderr / stdout, necesita hacer esto de la manera habitual con tempfiles. (Hay maneras de hacer esto tales, que interrumpir el shell normalmente no deja residuos atrás, pero esto es complejo y merece su propia respuesta.)

P: ¿Versión Bash?

  • Creo que necesitas Bash 4 y superior (debido a printf %q)

P: Esto todavía se ve así torpe.

  • Correcto. Otra respuesta aquí muestra cómo se puede hacer en ksh mucho más limpiamente. Sin embargo, no estoy acostumbrado a ksh, así que dejo que otros creen una receta similar fácil de reutilizar para ksh.

P: ¿Por qué no usar ksh entonces?

  • Porque esta es una solución bash

P: El script se puede mejorar

  • Por supuesto, puede exprimir algunos bytes y crear más pequeños o más solución incomprensible. Solo tienes que ir a por ello;)

P: Hay un error tipográfico. : catch STDOUT STDERR cmd args.. debe decir # catch STDOUT STDERR cmd args..

  • En realidad esto es lo que se pretende. : aparece en bash -x mientras los comentarios se tragan silenciosamente. Así que puedes ver dónde está el analizador si tienes un error tipográfico en la definición de la función. Es un viejo truco de depuración. Pero ten cuidado un poco, puedes crear fácilmente algunos efectos secundarios dentro de los argumentos de :.

Editar: Se agregaron un par más ; para que sea más fácil crear un solo revestimiento a partir de catch(). Y se agregó la sección cómo funciona.

 11
Author: Tino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:38

Técnicamente, los pipes con nombre no son archivos temporales y nadie los menciona aquí. No almacenan nada en el sistema de archivos y puedes eliminarlos tan pronto como los conectes (para que nunca los veas):

#!/bin/bash -e

foo () {
    echo stdout1
    echo stderr1 >&2
    sleep 1
    echo stdout2
    echo stderr2 >&2
}

rm -f stdout stderr
mkfifo stdout stderr
foo >stdout 2>stderr &             # blocks until reader is connected
exec {fdout}<stdout {fderr}<stderr # unblocks `foo &`
rm stdout stderr                   # filesystem objects are no longer needed

stdout=$(cat <&$fdout)
stderr=$(cat <&$fderr)

echo $stdout
echo $stderr

exec {fdout}<&- {fderr}<&- # free file descriptors, optional

Puede tener múltiples procesos en segundo plano de esta manera y recopilar asincrónicamente sus stdouts y stderrs en un momento conveniente, etc.

Si necesita esto para un solo proceso, también puede usar números fd codificados como 3 y 4, en lugar de la sintaxis {fdout}/{fderr} (que encuentra un fd gratis para usted).

 6
Author: Irfy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-16 09:06:33

A

No le gustó la evaluación, así que aquí hay una solución que usa algunos trucos de redirección para capturar la salida del programa a una variable y luego analiza esa variable para extraer los diferentes componentes. El indicador-w establece el tamaño del fragmento e influye en el orden de los mensajes std-out/err en el formato intermedio. 1 da potencialmente alta resolución a costa de gastos generales.

#######                                                                                                                                                                                                                          
# runs "$@" and outputs both stdout and stderr on stdin, both in a prefixed format allowing both std in and out to be separately stored in variables later.                                                                  
# limitations: Bash does not allow null to be returned from subshells, limiting the usefullness of applying this function to commands with null in the output.                                                                   
# example:                                                                                                                                                                                                                       
#  var=$(keepBoth ls . notHere)                                                                                                                                                                                                  
#  echo ls had the exit code "$(extractOne r "$var")"                                                                                                                                                                            
#  echo ls had the stdErr of "$(extractOne e "$var")"                                                                                                                                                                            
#  echo ls had the stdOut of "$(extractOne o "$var")"                                                                                                                                                                            
keepBoth() {                                                                                                                                                                                                                     
  (                                                                                                                                                                                                                              
    prefix(){                                                                                                                                                                                                                    
      ( set -o pipefail                                                                                                                                                                                                          
        base64 -w 1 - | (                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
          while read c                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
          do echo -E "$1" "$c"                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
          done                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
        )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
      )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
    }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    ( (                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
        "$@" | prefix o >&3                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
        echo  ${PIPESTATUS[0]} | prefix r >&3                                                                                                                                                                                                                                                                                                                                                                                                                                                           
      ) 2>&1 | prefix e >&1                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
    ) 3>&1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
  )                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

extractOne() { # extract                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
  echo "$2" | grep "^$1" | cut --delimiter=' ' --fields=2 | base64 --decode -                                                                                                                                                                                                                                                                                                                                                                                                                           
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
 3
Author: mncl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-08 10:41:34

Sucintamente, creo que la respuesta es 'No'. La captura $( ... ) solo captura la salida estándar de la variable; no hay una manera de obtener el error estándar capturado en una variable separada. Por lo tanto, lo que tienes es casi tan limpio como se pone.

 2
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-14 06:33:42

Qué pasa... = D

GET_STDERR=""
GET_STDOUT=""
get_stderr_stdout() {
    GET_STDERR=""
    GET_STDOUT=""
    unset t_std t_err
    eval "$( (eval $1) 2> >(t_err=$(cat); typeset -p t_err) > >(t_std=$(cat); typeset -p t_std) )"
    GET_STDERR=$t_err
    GET_STDOUT=$t_std
}

get_stderr_stdout "command"
echo "$GET_STDERR"
echo "$GET_STDOUT"
 2
Author: Eduardo Lucio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-10 17:04:52

Para el beneficio del lector aquí hay una solución usando tempfile s.

La cuestión era no usar tempfile s. Sin embargo, esto podría deberse a la contaminación no deseada de /tmp/ con tempfile en caso de que el shell muera. En el caso de kill -9 algunos trap 'rm "$tmpfile1" "$tmpfile2"' 0 no se disparan.

Si se encuentra en una situación en la que puede utilizar tempfile, pero quiero nunca dejar escombros detrás , aquí hay una receta.

De nuevo se llama catch() (como mi otra respuesta) y tiene la misma sintaxis de llamada:

catch stdout stderr command args..

# Wrappers to avoid polluting the current shell's environment with variables

: catch_read returncode FD variable
catch_read()
{
eval "$3=\"\`cat <&$2\`\"";
# You can use read instead to skip some fork()s.
# However read stops at the first NUL byte,
# also does no \n removal and needs bash 3 or above:
#IFS='' read -ru$2 -d '' "$3";
return $1;
}
: catch_1 tempfile variable comand args..
catch_1()
{
{
rm -f "$1";
"${@:3}" 66<&-;
catch_read $? 66 "$2";
} 2>&1 >"$1" 66<"$1";
}

: catch stdout stderr command args..
catch()
{
catch_1 "`tempfile`" "${2:-stderr}" catch_1 "`tempfile`" "${1:-stdout}" "${@:3}";
}

Lo que hace:

  • Crea dos tempfile s para stdout y stderr. Sin embargo, casi inmediatamente elimina estos, de tal manera que solo están alrededor por un tiempo muy corto.

  • catch_1() captura stdout (FD 1) en una variable y se mueve stderr a stdout, de modo que el siguiente ("izquierdo") catch_1 puede capturar eso.

  • El procesamiento en catch se realiza de derecha a izquierda, por lo que el catch_1 izquierdo se ejecuta el último y capturas stderr.

Lo peor que puede suceder es que algunos archivos temporales aparezcan en /tmp/, pero siempre están vacíos en ese caso. (Se quitan antes de que se llenen.). Por lo general, esto no debería ser un problema, ya que bajo Linux tmpfs soporta aproximadamente 128K archivos por GB de memoria principal.

  • El comando dado también puede acceder y alterar todas las variables locales del shell. Así que usted puede llamar a una función de shell que tiene sideffects!

  • Esto solo se bifurca dos veces para la llamada tempfile.

Errores:

  • Falta un buen manejo de errores en caso de que tempfile falle.

  • Esto hace la eliminación habitual \n del shell. Véase el comentario en catch_read().

  • No puede usar descriptor de archivo 66 para canalizar datos a su comando. Si necesita eso, use otro descriptor para la redirección, como 42 (tenga en cuenta que los shells muy antiguos solo ofrecen FDs hasta 9).

  • Esto no puede manejar bytes NUL ($'\0') en stdout y stderr. (NUL es simplemente ignorado. Para la variante read se ignora todo lo que hay detrás de un NUL.)

PARA su información:

  • Unix nos permite acceder a los archivos eliminados, siempre y cuando mantenga alguna referencia a ellos (como un manejador de archivos abierto). De esta manera podemos abrirlos y luego eliminarlos.
 2
Author: Tino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:48

Si el comando 1) sin efectos secundarios con estado y 2) es computacionalmente barato, la solución más fácil es ejecutarlo dos veces. He utilizado principalmente esto para el código que se ejecuta durante la secuencia de arranque cuando aún no sabes si el disco va a funcionar. En mi caso, era un pequeño some_command por lo que no hubo un golpe de rendimiento para correr dos veces, y el comando no tuvo efectos secundarios.

El principal beneficio es que es limpio y fácil de leer. Las soluciones aquí son bastante inteligentes, pero odiaría ser el que tiene que mantener un script que contenga las soluciones más complicadas. Recomendaría el enfoque simple de ejecutar dos veces si su escenario funciona con eso, ya que es mucho más limpio y más fácil de mantener.

Ejemplo:

output=$(getopt -o '' -l test: -- "$@")
errout=$(getopt -o '' -l test: -- "$@" 2>&1 >/dev/null)
if [[ -n "$errout" ]]; then
        echo "Option Error: $errout"
fi

Nuevamente, esto solo está bien porque getopt no tiene efectos secundarios. Sé que es seguro para el rendimiento porque mi código de padres llama a esto menos de 100 veces durante todo el programa, y el usuario nunca notará 100 llamadas getopt vs 200 llamadas getopt.

 0
Author: Hamy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-02 04:14:58

Aquí hay una variación más simple que no es exactamente lo que el OP quería, pero es diferente a cualquiera de las otras opciones. Puede obtener lo que quiera reorganizando los descriptores de archivo.

Comando de prueba:

%> cat xx.sh  
#!/bin/bash
echo stdout
>&2 echo stderr

Que por sí mismo hace:

%> ./xx.sh
stdout
stderr

Ahora, imprima stdout, capture stderr en una variable y registre stdout en un archivo

%> export err=$(./xx.sh 3>&1 1>&2 2>&3 >"out")
stdout
%> cat out    
stdout
%> echo
$err 
stderr

O registre stdout y capture stderr en una variable:

export err=$(./xx.sh 3>&1 1>out 2>&3 )
%> cat out
stdout
%> echo $err
stderr

Entiendes la idea.

 0
Author: Bruce Edge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-05 01:28:25

Una solución alternativa, que es hacky pero quizás más intuitiva que algunas de las sugerencias de esta página, es etiquetar los flujos de salida, fusionarlos y dividirlos después en función de las etiquetas. Por ejemplo, podríamos etiquetar stdout con un prefijo "STDOUT":

function someCmd {
    echo "I am stdout"
    echo "I am stderr" 1>&2
}

ALL=$({ someCmd | sed -e 's/^/STDOUT/g'; } 2>&1)
OUT=$(echo "$ALL" | grep    "^STDOUT" | sed -e 's/^STDOUT//g')
ERR=$(echo "$ALL" | grep -v "^STDOUT")

```

Si sabe que stdout y/o stderr son de forma restringida, puede crear una etiqueta que no entre en conflicto con su contenido permitido.

 0
Author: Warbo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-02 21:19:54

ADVERTENCIA: NO (todavía?) ¡TRABAJANDO!

Lo siguiente parece una posible pista para que funcione sin crear archivos temporales y también solo en POSIX sh; sin embargo, requiere base64 y debido a la codificación/decodificación puede no ser tan eficiente y usar también memoria "más grande".

  • Incluso en el caso simple, ya fallaría, cuando la última línea stderr no tiene nueva línea. Esto se puede arreglar al menos en algunos casos reemplazando exe por " { exe; echo > & 2;}", es decir, agregando un nueva línea.
  • El principal problema es, sin embargo, que todo parece picante. Intenta usar un exe como:

    Exe() { cat / usr/share/hunspell / de_DE.dic cat / usr/share/hunspell / en_GB.dic > & 2 }

Y verás que, por ejemplo, partes de la línea codificada base64 está en la parte superior del archivo, partes al final, y las cosas stderr no decodificadas en el medio.

Bueno, incluso si la idea de abajo no puede funcionar (lo cual asumo), puede servir como un anti-ejemplo para las personas que pueden creer falsamente que se podría hacer que funcione así.

Idea (o anti-ejemplo):

#!/bin/sh

exe()
{
        echo out1
        echo err1 >&2
        echo out2
        echo out3
        echo err2 >&2
        echo out4
        echo err3 >&2
        echo -n err4 >&2
}


r="$(  { exe  |  base64 -w 0 ; }  2>&1 )"

echo RAW
printf '%s' "$r"
echo RAW

o="$( printf '%s' "$r" | tail -n 1 | base64 -d )"
e="$( printf '%s' "$r" | head -n -1  )"
unset r    

echo
echo OUT
printf '%s' "$o"
echo OUT
echo
echo ERR
printf '%s' "$e"
echo ERR

Da (con la corrección stderr-newline):

$ ./ggg 
RAW
err1
err2
err3
err4

b3V0MQpvdXQyCm91dDMKb3V0NAo=RAW

OUT
out1
out2
out3
out4OUT

ERR
err1
err2
err3
err4ERR

(Al menos en dash y bash de Debian)

 0
Author: calestyo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-27 18:13:42