2011-05-16 12:14:48 +0000 2011-05-16 12:14:48 +0000
247
247

Bash: Iterar sobre líneas en una variable

¿Cómo se itera adecuadamente sobre líneas en bash ya sea en una variable, o desde la salida de un comando? Simplemente estableciendo 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 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 puedes ver, haciendo eco de la variable o iterando sobre el comando cat imprime cada una de las líneas una a una correctamente. Sin embargo, el primero para el bucle imprime todos los elementos en una sola línea. ¿Alguna idea?

Respuestas (5)

312
312
312
2011-05-16 13:47:36 +0000

Con bash, si quieres incrustar nuevas líneas en una cadena, encierra 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 tienes una cadena de este tipo en una variable, puedes leerla línea por línea con:

while IFS= read -r line; do
    echo "... $line ..."
done <<< "$list"
72
72
72
2011-05-16 12:21:28 +0000

Puedes usar while + read:

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

Btw. la opción de -e a echo no es estándar. Usa printf en su lugar, si quieres la portabilidad.

32
32
32
2014-03-17 21:45:33 +0000
#!/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
15
15
15
2011-05-16 12:45:14 +0000

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

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

Un poco más sensato/leíble sería:

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

Pero eso es demasiado complejo, sólo necesitas un espacio ahí:

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

$line variable no contiene nuevas líneas. Contiene instancias de `Aquí hay una forma divertida de hacer tu for loop:

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

Un poco más sensato/leíble sería:

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

Pero eso es demasiado complejo, sólo necesitas un espacio ahí:

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

$line variable no contiene nuevas líneas. Contiene instancias de seguidas de n. Puedes verlo 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 está reemplazando aquellos con espacios, lo que es suficiente para que funcione en los 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

Demo:

$ 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
3
3
3
2019-05-07 16:19:00 +0000

También puedes convertir primero la variable en un array, y luego iterar sobre esto.

lines="abc
def
ghi"

declare -a theArray

while read -r line
do
    theArray+=($line)            
done <<< "$lines"

for line in "${theArray[@]}"
do
    echo "$line"
    #Do something complex here that would break your read loop
done

Esto sólo es útil si no quieres meterte con el IFS y también tienes problemas con el comando read, como puede suceder, si llamas a otro script dentro del bucle que ese script puede vaciar tu buffer de lectura antes de volver, como me pasó a mí.