Bash: Zeilen in einer Variablen durchlaufen

225

Wie iteriert man richtig über Zeilen in Bash, entweder in einer Variablen oder aus der Ausgabe eines Befehls? Das einfache Setzen der IFS-Variablen auf eine neue Zeile funktioniert für die Ausgabe eines Befehls, nicht jedoch beim Verarbeiten einer Variablen, die neue Zeilen enthält.

Zum Beispiel

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

Dies ergibt die Ausgabe:

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

Wie Sie sehen können, werden beim Echo der Variablen oder beim Iterieren über den catBefehl die einzelnen Zeilen korrekt gedruckt. Die erste for-Schleife druckt jedoch alle Elemente in einer einzelnen Zeile. Irgendwelche Ideen?

Alex Spurling
quelle
Nur ein Kommentar für alle Antworten: Ich musste $ (echo "$ line" | sed -e 's / ^ [[: space:]] * //') ausführen, um das Zeilenumbruchzeichen zu trimmen.
Servermanfail

Antworten:

290

Wenn Sie mit bash Zeilenumbrüche in eine Zeichenfolge einbetten möchten, schließen Sie die Zeichenfolge ein mit $'':

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

Und wenn Sie eine solche Zeichenfolge bereits in einer Variablen haben, können Sie sie zeilenweise lesen mit:

while read -r line; do
    echo "... $line ..."
done <<< "$list"
Glenn Jackman
quelle
30
Nur eine Anmerkung, dass das done <<< "$list"entscheidend ist
Jason Axelson
21
Der Grund done <<< "$list"dafür ist, dass "$ list" als Eingabe anread
wisbucky
22
Ich muss das betonen: ZWEIFACHES ANGEBOT $listist sehr wichtig.
André Chalella
8
echo "$list" | while ...könnte klarer erscheinen als... done <<< "$line"
kyb
5
Dieser Ansatz hat Nachteile, da die while-Schleife in einer Subshell ausgeführt wird: Alle in der Schleife zugewiesenen Variablen bleiben nicht erhalten.
Glenn Jackman
66

Sie können while+ verwenden read:

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

Btw. Die -eOption zu echoist nicht standard. Verwenden Sie printfstattdessen, wenn Sie Portabilität wünschen.

maxelost
quelle
12
Beachten Sie, dass bei Verwendung dieser Syntax die in der Schleife zugewiesenen Variablen nicht hinter der Schleife zurückbleiben. Seltsamerweise <<<funktioniert die von Glenn Jackman vorgeschlagene Version mit variabler Zuweisung.
Sparhawk
7
@Sparhawk Ja, das liegt daran, dass die Pipe eine Unterschale startet, die das whileTeil ausführt . Die <<<Version funktioniert nicht (zumindest in neuen Bash-Versionen).
Maxelost
26
#!/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
niemand wichtig
quelle
2
Dies gibt alle Elemente aus, keine Zeilen.
Tomasz Posłuszny
4
@ TomaszPosłuszny Nein, hiermit werden 3 Zeilen auf meinem Computer ausgegeben (es werden 3 Zeilen gezählt). Beachten Sie die Einstellung des IFS. Sie könnten auch IFS=$'\n'für den gleichen Effekt verwenden.
JMISEREZ
11

Hier ist eine lustige Methode, um Ihre for-Schleife zu erstellen:

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

Ein wenig sinnvoller / lesbarer wäre:

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

Aber das ist alles zu komplex, Sie brauchen nur einen Platz darin:

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

Sie $lineVariable enthält keine Zeilenumbrüche. Es enthält Instanzen von \gefolgt von n. Das können Sie deutlich sehen mit:

$ 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

Die Ersetzung ersetzt diese durch Leerzeichen, was ausreicht, um for-Schleifen zu bearbeiten:

$ 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
Matte
quelle
Interessant, können Sie erklären, was hier los ist? Sie ersetzen anscheinend \ n durch eine neue Zeile ... Was ist der Unterschied zwischen der ursprünglichen und der neuen Zeichenfolge?
Alex Spurling
@Alex: habe meine Antwort aktualisiert - auch mit einer einfacheren Version :-)
Mat
Ich habe es ausprobiert und es scheint nicht zu funktionieren, wenn Ihre Eingabe Leerzeichen enthält. Im obigen Beispiel haben wir "one \ ntwo \ nthree" und das funktioniert, aber es schlägt fehl, wenn wir "entry one \ nentry two \ nentry three" haben, da es auch eine neue Zeile für das Leerzeichen hinzufügt.
John Rocha
2
Nur eine Notiz, die crSie verwenden können , anstatt sie zu definieren $'\n'.
Devios1
1

Sie können die Variable auch zuerst in ein Array konvertieren und dann darüber iterieren.

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

Dies ist nur dann nützlich, wenn Sie sich nicht IFSmit dem readbefehl anlegen möchten und auch Probleme mit dem befehl haben, da es passieren kann, dass Sie ein anderes Skript in der Schleife aufrufen, das Ihren Lesepuffer vor der Rückkehr leeren kann, wie es mir passiert ist .

Torge
quelle