Pregunta Bash: iteración sobre líneas en una variable


¿Cómo se puede iterar correctamente sobre líneas en bash, ya sea en una variable, o desde la salida de un comando? Simplemente establecer la variable IFS en una nueva línea funciona para la salida de un comando, pero no cuando se procesa una variable que contiene nuevas líneas.

Por ejemplo

#!/bin/bash

list="One\ntwo\nthree\nfour"

#Print the list with echo
echo -e "echo: \n$list"

#Set the field separator to new line
IFS=$'\n'

#Try to iterate over each line
echo "For loop:"
for item in $list
do
        echo "Item: $item"
done

#Output the variable to a file
echo -e $list > list.txt

#Try to iterate over each line from the cat command
echo "For loop over command output:"
for item in `cat list.txt`
do
        echo "Item: $item"
done

Esto le da la salida:

echo: 
One
two
three
four
For loop:
Item: One\ntwo\nthree\nfour
For loop over command output:
Item: One
Item: two
Item: three
Item: four

Como puede ver, haciendo eco de la variable o iterando sobre el cat comando imprime cada una de las líneas una por una correctamente. Sin embargo, el primer bucle imprime todos los elementos en una sola línea. ¿Algunas ideas?


167


origen




Respuestas:


Con bash, si desea insertar nuevas líneas en una cadena, encierre la cadena con $'':

$ list="One\ntwo\nthree\nfour"
$ echo "$list"
One\ntwo\nthree\nfour
$ list=$'One\ntwo\nthree\nfour'
$ echo "$list"
One
two
three
four

Y si ya tiene una cadena así en una variable, puede leerla línea por línea con:

while read -r line; do
    echo "... $line ..."
done <<< "$list"

208



Solo una nota que el done <<< "$list" Es crucial - Jason Axelson
La razón done <<< "$list" es crucial porque eso pasará "$ list" como la entrada a read - wisbucky
Necesito enfatizar esto: DOBLE COTIZACIÓN $list es muy crucial. - André Chalella
¿Cómo haría eso en cenizas? Porque tengo un syntax error: unexpected redirection. - atripes
Hay desventajas en ese enfoque, ya que el ciclo while se ejecutará en una subcapa: las variables asignadas en el ciclo no persistirán. - glenn jackman


Puedes usar while + read:

some_command | while read line ; do
   echo === $line ===
done

Por cierto. el -e opción de echo no es estándar Utilizar printf en cambio, si quieres portabilidad.


47



Tenga en cuenta que si usa esta sintaxis, las variables asignadas dentro del bucle no se pegarán después del bucle. Curiosamente, el <<< versión sugerida por Glenn Jackman hace trabajar con la asignación de variables. - Sparhawk
@Sparhawk Sí, eso es porque la tubería inicia una subcadena que ejecuta el while parte. los <<< la versión no (en nuevas versiones bash, al menos). - maxelost


#!/bin/sh

items="
one two three four
hello world
this should work just fine
"

IFS='
'
count=0
for item in $items
do
  count=$((count+1))
  echo $count $item
done

18



Esto produce todos los elementos, no líneas. - Tomasz Posłuszny
@ TomaszPosłuszny No, esto genera 3 líneas (conteo es 3) en mi máquina. Tenga en cuenta la configuración de la IFS. También podrías usar IFS=$'\n' para el mismo efecto - jmiserez


Aquí hay una forma divertida de hacer tu bucle for:

for item in ${list//\\n/
}
do
   echo "Item: $item"
done

Un poco más sensato / legible sería:

cr='
'
for item in ${list//\\n/$cr}
do
   echo "Item: $item"
done

Pero eso es demasiado complejo, solo necesitas un espacio allí:

for item in ${list//\\n/ }
do
   echo "Item: $item"
done

$line variable no contiene líneas nuevas. Contiene instancias de \ seguido por n. Puedes ver eso claramente con:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo $list | hexdump -C

$ ./t.sh
00000000  4f 6e 65 5c 6e 74 77 6f  5c 6e 74 68 72 65 65 5c  |One\ntwo\nthree\|
00000010  6e 66 6f 75 72 0a                                 |nfour.|
00000016

La sustitución reemplaza aquellos con espacios, lo cual es suficiente para que funcione en bucles:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo ${list//\\n/ } | hexdump -C

$ ./t.sh 
00000000  4f 6e 65 20 74 77 6f 20  74 68 72 65 65 20 66 6f  |One two three fo|
00000010  75 72 0a                                          |ur.|
00000013

Manifestación:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo ${list//\\n/ } | hexdump -C
for item in ${list//\\n/ } ; do
    echo $item
done

$ ./t.sh 
00000000  4f 6e 65 20 74 77 6f 20  74 68 72 65 65 20 66 6f  |One two three fo|
00000010  75 72 0a                                          |ur.|
00000013
One
two
three
four

7



Interesante, ¿puedes explicar lo que está pasando aquí? Parece que estás reemplazando \ n con una nueva línea ... ¿Cuál es la diferencia entre la cadena original y la nueva? - Alex Spurling
@Alex: actualicé mi respuesta, también con una versión más simple :-) - Mat
Probé esto y parece que no funciona si tienes espacios en tu entrada. En el ejemplo anterior, tenemos "one \ ntwo \ nthree" y funciona, pero falla si tenemos "entry one \ nentry two \ nentry three", ya que también agrega una nueva línea para el espacio. - John Rocha
Solo una nota que en vez de definir cr puedes usar $'\n'. - devios1