¿Salida de comando dividido por salto de línea?


9

Tengo un comando que devuelve varias líneas. Para un procesamiento adicional, necesito procesar cada línea individual de esas líneas.

Mi código actual funciona modificando el IFS ( separador de campo interno ):

ROWS=$(some command returning multiple lines)

O=$IFS #save original IFS
IFS=$(echo -en "\n\b") # set IFS to linebreak

for ROW in ${ROWS[@]}
do
  echo "$ROW"
done

IFS=$O #restore old IFS

Me pregunto, ¿hay una mejor manera de acceder a las líneas individuales de la salida de varias líneas, una sin modificar el IFS? Especialmente la legibilidad de mi script se vuelve mala al modificar el IFS.

Actualización: Tengo problemas para que las respuestas funcionen, por ejemplo, la de choroba:

while IFS= read -r line ; do
    let var+=line #line 42
done << $(sqlite3 -list -nullvalue NULL -separator ',' /var/log/asterisk/master.db "${QUERY}")
echo "$var" # line 44

me da

./bla.sh: row 44: Warning: here-document at line 43 delimited by end-of-file (wanted `$(sqlite3 -list -nullvalue NULL -separator , /var/log/asterisk/master.db ${QUERY})')
./bla.sh: row 42: let: echo "": syntax error: invalid arithmetic operator. (error causing character is \"""\").

Alguien me puede ayudar con esto? ¡Gracias!


La respuesta de choroba dice hacer < <(some command returning multiple lines), pero eso no es lo que estás haciendo.
Mikel

Respuestas:


13

¿Qué pasa readen un whilebucle?

some command returning multiple lines | while IFS= read -r line ; do
    echo "$line"
done

Sin embargo, tenga cuidado, la tubería se ejecuta en una subshell, lo que significa que no puede cambiar los valores de las variables para el resto del script. Si necesita algo para sobrevivir de su ciclo while, puede usar la sustitución del proceso de bash:

while IFS= read -r line ; do
    let var+=line
done < <(some command returning multiple lines)
echo "$var"

El uso de la sustitución de procesos es una solución inteligente para esto bash, pero vale la pena señalar que el alcance del subshell de las variables es solo un problema bash, ya que hacer lo mismo zshno tendría este problema en absoluto.
Caleb

ksh tampoco tiene este problema.
Didi Kohen

gracias chicos, probé esta solución, pero no puedo hacer que funcione, vea mi publicación original actualizada, ¡gracias!
stefan.at.wpf

@ stefan.at.wpf: No se pueden colocar espacios y signos de dólar a voluntad. Tienen significado.
choroba

3

Establecer IFSsolo una nueva línea no es suficiente. (¿Por qué también se divide en los caracteres de retroceso, por cierto?)

En su código, ${ROWS[@]}(que es una forma extraña de escribir $ROWS, ROWSno es una matriz) no se cita entre comillas dobles. (Si estaba entre comillas dobles, obtendría una sola cadena, ya ROWSque no es una matriz). Por lo tanto, el shell divide el valor de la variable en campos en cada IFScarácter, luego trata cada campo como un patrón global. Por ejemplo, si una de las líneas impresas por el comando contiene un solo carácter *, este será reemplazado por los nombres de archivo en el directorio actual.

Puedes desactivar el globbing con set -f. En la mayoría de los casos en los que configura el IFSuso de la función de división de campo del shell, también debe desactivar el globbing. Vuelva a encenderlo con set +f.

El idioma confiable para leer la salida de un comando línea por línea es while IFS= read -r.

some command returning multiple lines |
while IFS= read -r ROW; do
  
done

Tenga en cuenta que la mayoría de los shells ejecutan cada comando de una tubería en un subshell separado. Entonces, si necesita establecer variables y usarlas después del ciclo, ajuste estos comandos en un grupo junto con el ciclo. (Ksh y zsh son las excepciones, ejecutan el último comando de una tubería en el shell principal).

some command returning multiple lines | {
  while IFS= read -r ROW; do
    
    row_count=$((row_count+1))
  done
  echo "There were $row_count rows."
}

1

Está mezclando la sintaxis here-document y here-string en su pregunta de actualización.

Utilice el documento aquí:

while IFS= read -r line ; do
    let var+=line #line 42
done <<ENDMARK
$(sqlite3 -list -nullvalue NULL -separator ',' /var/log/asterisk/master.db "${QUERY}")
ENDMARK

O aquí-cadena:

while IFS= read -r line ; do
    let var+=line #line 42
done <<< $(sqlite3 -list -nullvalue NULL -separator ',' /var/log/asterisk/master.db "${QUERY}")
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.