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

Bash: Iterare su linee in una variabile

Come si itera correttamente su linee in bash sia in una variabile, sia dall'uscita di un comando? Semplicemente impostando la variabile IFS su una nuova linea funziona per l'uscita di un comando, ma non quando si elabora una variabile che contiene nuove linee.

Per esempio

#!/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
``` ```
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

Questo dà l'uscita:

&001

Come si può vedere, l'eco della variabile o l'iterazione sopra il comando cat stampa correttamente ciascuna delle linee una per una. Tuttavia, il primo per il loop stampa tutti gli elementi su una singola linea. Qualche idea?

Risposte (5)

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

Con bash, se volete incorporare nuove linee in una stringa, racchiudere la stringa con $'':

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

E se avete già una tale stringa in una variabile, potete leggerla linea per linea con:

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

È possibile utilizzare while + read:

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

Btw. l'opzione -e a echo non è standard. Utilizzate invece printf se volete la portabilità.

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

Ecco un modo divertente di fare il vostro per il loop:

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

Un po’ più sensato / leggibile sarebbe:

for item in ${list//\n/ }
do
   echo "Item: $item"
done
``` ```
$ 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

Ma è tutto troppo complesso, serve solo uno spazio lì dentro:

$ 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

La variabile You $line non contiene nuove linee. Contiene istanze di `Ecco un modo divertente di fare il vostro per il loop:

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

Un po’ più sensato / leggibile sarebbe:

for item in ${list//\n/ }
do
   echo "Item: $item"
done
``` ```
$ 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

Ma è tutto troppo complesso, serve solo uno spazio lì dentro:

$ 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

La variabile You $line non contiene nuove linee. Contiene istanze di seguite dan. Lo si vede chiaramente con:

$ 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
``` &001 


La sostituzione sta sostituendo quelle con spazi, il che è sufficiente perché funzioni per i loop: 


&001 


* * * * 


Demo: 


&001
3
3
3
2019-05-07 16:19:00 +0000

Si può anche convertire prima la variabile in un array, poi iterare su questo.

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

Questo è utile solo se non si vuole pasticciare con il IFS e si hanno problemi con il comando read, come può succedere, se si chiama un altro script all'interno del ciclo che quello script può svuotare il buffer di lettura prima di tornare, come è successo a me.