Capturando múltiples salidas de línea en una variable Bash

583

Tengo un script 'myscript' que genera lo siguiente:

abc
def
ghi

en otro script, llamo:

declare RESULT=$(./myscript)

y $RESULTobtiene el valor

abc def ghi

¿Hay alguna manera de almacenar el resultado con las nuevas líneas o con el carácter '\ n' para que pueda generarlo con ' echo -e'?

Parker
fuente
1
Me sorprende. ¿no tienes $ (cat ./myscipt)? de lo contrario habría esperado que al intentar ejecutar comandos abc, def y ghi
Johannes Schaub - litb
@litb: sí, supongo que sí; También puede usar $ (<./ myscript) que evita ejecutar un comando.
Jonathan Leffler
2
(NB: los dos comentarios anteriores se refieren a una revisión de la pregunta que comenzó . Tengo un script 'myscript' que contiene lo siguiente , que condujo a las preguntas. La revisión actual de la pregunta ( Tengo un script ' miscript' que da salida a la siguiente ) hace que los comentarios superfluos Sin embargo, la revisión es de 2011-11-11, mucho después de que se hicieron las dos observaciones..
Jonathan Leffler

Respuestas:

1083

En realidad, RESULT contiene lo que desea: demostrar:

echo "$RESULT"

Lo que muestras es lo que obtienes de:

echo $RESULT

Como se señaló en los comentarios, la diferencia es que (1) la versión de doble comilla de la variable ( echo "$RESULT") conserva el espacio interno del valor exactamente como se representa en la variable: líneas nuevas, pestañas, espacios en blanco múltiples y todo, mientras que (2 ) la versión sin comillas ( echo $RESULT) reemplaza cada secuencia de uno o más espacios en blanco, pestañas y líneas nuevas con un solo espacio. Por lo tanto (1) conserva la forma de la variable de entrada, mientras que (2) crea una única línea de salida potencialmente muy larga con 'palabras' separadas por espacios individuales (donde una 'palabra' es una secuencia de caracteres que no son espacios en blanco; no es necesario No habrá alfanuméricos en ninguna de las palabras).

Jonathan Leffler
fuente
69
@troelskn: la diferencia es que (1) la versión de doble comilla de la variable conserva el espacio interno del valor exactamente como se representa en la variable, líneas nuevas, pestañas, espacios en blanco múltiples y todo, mientras que (2) la versión sin comillas reemplaza cada secuencia de uno o más espacios en blanco, pestañas y líneas nuevas con un solo espacio. Por lo tanto (1) conserva la forma de la variable de entrada, mientras que (2) crea una única línea de salida potencialmente muy larga con 'palabras' separadas por espacios individuales (donde una 'palabra' es una secuencia de caracteres que no son espacios en blanco; no es necesario No habrá alfanuméricos en ninguna de las palabras).
Jonathan Leffler
23
Para que la respuesta sea más fácil de entender: la respuesta dice que echo "$ RESULT" conserva la nueva línea, mientras que echo $ RESULT no.
Yu Shen
Esto no puede preservar nuevas líneas y espacios iniciales en algunas situaciones.
CommaToast
3
@CommaToast: ¿Vas a elaborar sobre eso? Las nuevas líneas finales se pierden; No hay una manera fácil de evitar eso. Espacios en blanco principales: no conozco ninguna circunstancia en la que se pierdan.
Jonathan Leffler
90

Otro escollo con esto es que la sustitución de comandos - $()- elimina las líneas nuevas. Probablemente no siempre sea importante, pero si realmente desea conservar exactamente lo que se generó, deberá usar otra línea y algunas citas:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

Esto es especialmente importante si desea manejar todos los nombres de archivo posibles (para evitar comportamientos indefinidos como operar en el archivo incorrecto).

l0b0
fuente
44
Tenía que trabajar durante un tiempo con una cáscara rota que no se retire el último salto de línea desde la sustitución de comandos (que es no sustitución de proceso ), y se rompió casi todo. Por ejemplo, si lo hizo pwd=`pwd`; ls $pwd/$file, obtuvo una nueva línea antes del /, y encerrar el nombre entre comillas dobles no ayudó. Fue arreglado rápidamente. Esto fue en 1983-5, en ICL Perq PNX; el shell no tenía $PWDcomo variable incorporada.
Jonathan Leffler
19

En caso de que esté interesado en líneas específicas, use una matriz de resultados:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"
usuario2574210
fuente
3
Si hay espacios en las líneas, esto contará los campos (contenido entre espacios) en lugar de las líneas.
Liam
2
Se podría utilizar readarrayy la sustitución de proceso en lugar de la sustitución de comandos: readarray -t RESULT < <(./myscript>.
chepner
15

Además de la respuesta dada por @ l0b0, acabo de tener la situación en la que necesitaba mantener el resultado de las nuevas líneas al final del script y verificar el código de retorno del script. ¿Y el problema con la respuesta de l0b0 es que el 'echo x' estaba restableciendo $? volver a cero ... así que logré encontrar esta solución muy astuta:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"
Lurchman
fuente
Esto es encantador, en realidad tuve el caso de uso donde quiero tener una die ()función que acepte un código de salida arbitrario y, opcionalmente, un mensaje, y quería usar otra usage ()función para proporcionar el mensaje, pero las nuevas líneas se aplastaron, espero que esto me permita evitar eso.
dragon788
1

Después de probar la mayoría de las soluciones aquí, lo más fácil que encontré fue lo obvio: usar un archivo temporal. No estoy seguro de lo que quiere hacer con su salida de línea múltiple, pero luego puede manejarlo línea por línea usando lectura. Lo único que realmente no puede hacer es pegarlo fácilmente en la misma variable, pero para la mayoría de los propósitos prácticos es mucho más fácil de manejar.

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

Hack rápido para que haga la acción solicitada:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

Tenga en cuenta que esto agrega una línea adicional. Si trabajas en él, puedes codificarlo, soy demasiado vago.


EDITAR: Si bien este caso funciona perfectamente bien, las personas que lo lean deben ser conscientes de que puede aplastar fácilmente su stdin dentro del bucle while, lo que le dará un script que ejecutará una línea, borrará el stdin y saldrá. ¿Como ssh hará eso, creo? Lo acabo de ver recientemente, otros ejemplos de código aquí: /unix/24260/reading-lines-from-a-file-with-bash-for-vs-while

¡Una vez más! Esta vez con un identificador de archivo diferente (stdin, stdout, stderr son 0-2, por lo que podemos usar & 3 o superior en bash).

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

También puede usar mktemp, pero este es solo un ejemplo de código rápido. El uso de mktemp se ve así:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

Luego use $ filenamevar como lo haría con el nombre real de un archivo. Probablemente no necesite ser explicado aquí, pero alguien se quejó en los comentarios.

usuario1279741
fuente
También probé otras soluciones, con su primera sugerencia finalmente conseguí que mi script funcionara
Kar.ma
1
Voto negativo: esto es excesivamente complejo y no puede evitar múltiples bashtrampas comunes .
tripleee
Ya alguien me contó sobre el extraño problema de manejo de archivos stdin el otro día y yo estaba como "wow". Déjame agregar algo realmente rápido.
user1279741
En Bash, puede usar la readextensión de comando -u 3para leer el descriptor de archivo 3.
Jonathan Leffler
¿Por qué no mientras ... do ... done <<(. Myscript.sh)
jtgd
0

¡Qué tal esto, leerá cada línea en una variable y eso se puede usar posteriormente! digamos que la salida de myscript se redirige a un archivo llamado myscript_output

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'
Rahul Reddy
fuente
44
Bueno, eso no es bash, eso es awk.
vadipp