Frage Bash: Iterieren von Zeilen in einer Variablen


Wie iteriert man über Linien in bash entweder in einer Variablen oder von der Ausgabe eines Befehls? Das einfache Festlegen der IFS-Variablen auf eine neue Zeile funktioniert für die Ausgabe eines Befehls, aber nicht beim Verarbeiten einer Variablen, die neue Zeilen enthält.

Beispielsweise

#!/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 gibt 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, wird die Variable wiederholt oder über die cat Befehl druckt jede der Zeilen nacheinander korrekt. Die erste for-Schleife druckt jedoch alle Elemente in einer einzelnen Zeile. Irgendwelche Ideen?


167
2018-05-16 12:14


Ursprung




Antworten:


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

$ 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 Zeile für Zeile mit lesen:

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

208
2018-05-16 13:47



Nur eine Anmerkung, dass die done <<< "$list" ist entscheidend - Jason Axelson
Der Grund done <<< "$list" Entscheidend ist, dass das "$ list" als Eingabe übergibt read - wisbucky
Ich muss das betonen: DOUBLE-QUOTING $list ist sehr wichtig. - André Chalella
Wie würde ich das in Asche machen? Weil ich einen bekomme syntax error: unexpected redirection. - atripes
Es gibt Nachteile dieses Ansatzes, da die while-Schleife in einer Untershell ausgeführt wird: Alle in der Schleife zugewiesenen Variablen werden nicht beibehalten. - glenn jackman


Sie können verwenden while + read:

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

Übrigens. das -e Option zu echo ist nicht Standard. Benutzen printf stattdessen, wenn Sie Portabilität wünschen.


47
2018-05-16 12:21



Beachten Sie, dass bei Verwendung dieser Syntax Variablen, die innerhalb der Schleife zugewiesen sind, nicht nach der Schleife bleiben. Seltsamerweise, die <<< version von glenn jackman vorgeschlagen tut arbeite mit variabler Zuweisung. - Sparhawk
@Sparhawk Ja, das liegt daran, dass die Pipe eine Subshell startet, die das ausführt while Teil. Das <<< Version nicht (zumindest in neuen Bash-Versionen). - 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
2018-03-17 21:45



Dies gibt alle Elemente aus, nicht die Zeilen. - Tomasz Posłuszny
@ TomaszPosłuszny Nein, das gibt 3 (Anzahl ist 3) Zeilen auf meinem Rechner aus. Beachten Sie die Einstellung des IFS. Du könntest auch benutzen IFS=$'\n' für den gleichen Effekt. - jmiserez


Hier ist eine lustige Art, Ihre for-Schleife zu machen:

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

Ein bisschen sinnvoller / lesbarer wäre:

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

Aber das ist alles zu komplex, du brauchst nur einen Platz drin:

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

Sie $line Variable enthält keine Zeilenumbrüche. Es enthält Instanzen von \ gefolgt von n. Das kannst du klar 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 Substitution ersetzt diese durch Leerzeichen, was ausreicht, um in for-Schleifen zu arbeiten:

$ 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

7
2018-05-16 12:45



Interessant, kannst du erklären, was hier vor sich geht? Anscheinend ersetzen Sie \ n durch eine neue Zeile ... Was ist der Unterschied zwischen der ursprünglichen und der neuen Zeichenfolge? - Alex Spurling
@Alex: aktualisierte meine Antwort - mit einer einfacheren Version auch :-) - Mat
Ich habe es ausprobiert und es scheint nicht zu funktionieren, wenn Sie Leerzeichen in Ihrer Eingabe haben. 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 den Space hinzufügt. - John Rocha
Nur eine Notiz, die statt zu definieren cr Sie können verwenden $'\n'. - devios1