Wie kann ich Zeile für Zeile aus einer Variablen in Bash lesen?


48

Ich habe eine Variable, die mehrzeilige Ausgabe eines Befehls enthält. Was ist die effizienteste Methode, um die Ausgabe zeilenweise aus der Variablen zu lesen?

Zum Beispiel:

jobs="$(jobs)"
if [ "$jobs" ]; then
    # read lines from $jobs
fi

Antworten:


64

Sie können eine while-Schleife mit Prozessersetzung verwenden:

while read -r line
do
    echo "$line"
done < <(jobs)

Eine optimale Methode zum Lesen einer mehrzeiligen Variablen besteht darin, eine leere IFSVariable printffestzulegen und die Variable mit einem nachgestellten Zeilenumbruch zu versehen:

# Printf '%s\n' "$var" is necessary because printf '%s' "$var" on a
# variable that doesn't end with a newline then the while loop will
# completely miss the last line of the variable.
while IFS= read -r line
do
   echo "$line"
done < <(printf '%s\n' "$var")

Hinweis: Gemäß Shellcheck sc2031 ist die Verwendung von Prozesssubition einer Pipe vorzuziehen, um das [subtile] Erstellen einer Subshell zu vermeiden.

Beachten Sie außerdem, dass die Benennung der Variablen jobszu Verwirrung führen kann, da dies auch der Name eines allgemeinen Shell-Befehls ist.


3
Wenn Sie alle Leerzeichen while IFS= readread -r
beibehalten

Ich habe die Punkte festgelegt fred.bear erwähnt, sowie geändert echozu printf %s, so dass Ihr Skript funktionieren würde, auch bei nicht zahm Eingang.
Gilles 'SO - hör auf böse zu sein'

Um aus einer mehrzeiligen Variablen zu lesen, ist ein Herestring einem Piping aus printf vorzuziehen (siehe die Antwort von 10b0).
ata

1
@ata Obwohl ich das "vorzuziehen" oft genug gehört habe, muss beachtet werden, dass für einen Herestring immer ein beschreibbares Verzeichnis erforderlich ist /tmp, da es darauf ankommt , dass eine temporäre Arbeitsdatei erstellt werden kann. Sollten Sie sich jemals in einem eingeschränkten System befinden /tmp, das schreibgeschützt ist (und von Ihnen nicht geändert werden kann), werden Sie sich über die Möglichkeit freuen, eine alternative Lösung zu verwenden, z printf. B. mit der Pipe.
Syntaxfehler

1
Wenn die mehrzeilige Variable im zweiten Beispiel keinen abschließenden Zeilenumbruch enthält, geht das letzte Element verloren. Ändern Sie es auf:printf "%s\n" "$var" | while IFS= read -r line
David H. Bennett

26

So verarbeiten Sie die Ausgabe eines Befehls zeilenweise ( Erläuterung ):

jobs |
while IFS= read -r line; do
  process "$line"
done

Wenn Sie die Daten bereits in einer Variablen haben:

printf %s "$foo" | 

printf %s "$foo"ist fast identisch mit echo "$foo", gibt jedoch $foobuchstäblich aus, wobei dies als Option für den Echo-Befehl echo "$foo"interpretiert werden kann, $foowenn er mit einem beginnt -, und möglicherweise die Backslash-Sequenzen $fooin einigen Shells erweitert.

Beachten Sie, dass in einigen Shells (ash, bash, pdksh, aber nicht ksh oder zsh) die rechte Seite einer Pipeline in einem separaten Prozess ausgeführt wird, sodass alle in der Schleife festgelegten Variablen verloren gehen. Das folgende Skript für die Zeilenzählung gibt beispielsweise 0 in diesen Shells aus:

n=0
printf %s "$foo" |
while IFS= read -r line; do
  n=$(($n + 1))
done
echo $n

Eine Problemumgehung besteht darin, den Rest des Skripts (oder zumindest den Teil, der den Wert $naus der Schleife benötigt) in eine Befehlsliste einzufügen:

n=0
printf %s "$foo" | {
  while IFS= read -r line; do
    n=$(($n + 1))
  done
  echo $n
}

Wenn das Eingreifen in die nicht leeren Zeilen gut genug ist und die Eingabe nicht sehr umfangreich ist, können Sie die Wortteilung verwenden:

IFS='
'
set -f
for line in $(jobs); do
  # process line
done
set +f
unset IFS

Erläuterung: IFSWenn Sie eine einzelne Zeile als Zeilenumbruch festlegen, wird das Wort nur in Zeilenumbrüchen aufgeteilt (im Gegensatz zu Leerzeichen in der Standardeinstellung). set -fDeaktiviert das Globbing (dh die Platzhaltererweiterung), das andernfalls bei einer Befehlsersetzung $(jobs)oder einer Variablenersetzung auftreten würde $foo. Die forSchleife wirkt auf alle Teile von $(jobs), also alle nicht leeren Zeilen in der Befehlsausgabe. Stellen Sie abschließend die Globbing- IFSEinstellungen wieder her.


Ich hatte Probleme beim Einstellen von IFS und beim Deaktivieren von IFS. Ich denke, das Richtige ist, den alten Wert von IFS zu speichern und IFS auf diesen alten Wert zurückzusetzen. Ich bin kein Bash-Experte, aber meiner Erfahrung nach werden Sie auf das ursprüngliche Verhalten zurückgebracht.
Bjorn Roche

1
@ BjornRoche: innerhalb einer Funktion verwenden local IFS=something. Der Wert für den globalen Bereich wird nicht beeinflusst. IIRC unset IFSbringt Sie nicht auf die Standardeinstellungen zurück (und funktioniert auf keinen Fall, wenn dies vorher nicht die Standardeinstellungen war).
Peter Cordes

14

Problem: Wenn Sie eine while-Schleife verwenden, wird diese in der Subshell ausgeführt und alle Variablen gehen verloren. Lösung: Verwenden Sie für Schleife

# change delimiter (IFS) to new line.
IFS_BAK=$IFS
IFS=$'\n'

for line in $variableWithSeveralLines; do
 echo "$line"

 # return IFS back if you need to split new line by spaces:
 IFS=$IFS_BAK
 IFS_BAK=
 lineConvertedToArraySplittedBySpaces=( $line )
 echo "{lineConvertedToArraySplittedBySpaces[0]}"
 # return IFS back to newline for "for" loop
 IFS_BAK=$IFS
 IFS=$'\n'

done 

# return delimiter to previous value
IFS=$IFS_BAK
IFS_BAK=

ICH DANKE DIR SEHR!! Alle oben genannten Lösungen sind für mich gescheitert.
hax0r_n_code

Piping in eine while readSchleife in Bash bedeutet, dass sich die While-Schleife in einer Subshell befindet, sodass Variablen nicht global sind. while read;do ;done <<< "$var"macht den Schleifenkörper nicht zu einer Unterschale. (Letzte Bash hat eine Option, um den Körper einer cmd | whileSchleife nicht in eine Unterschale zu setzen, wie ksh es immer getan hat.)
Peter Cordes


10

Verwenden Sie in neueren Bash-Versionen mapfileoder readarray, um die Befehlsausgabe effizient in Arrays zu lesen

$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305

Haftungsausschluss: Ein schreckliches Beispiel, aber Sie können sich durchaus einen besseren Befehl einfallen lassen als Sie


Es ist eine schöne Möglichkeit, aber Würfe / var / tmp mit temporären Dateien auf meinem System. +1 sowieso
Eugene Yarmash

@Eugene: Das ist lustig. Auf welchem ​​System (Distribution / OS) läuft das?
sehe

Es ist FreeBSD 8. So reproduzieren Sie: Fügen Sie readarrayeine Funktion ein und rufen Sie die Funktion einige Male auf.
Eugene Yarmash

Schön, @sehe. +1
Teresa e Junior

7
jobs="$(jobs)"
while IFS= read
do
    echo $REPLY
done <<< "$jobs"

Verweise:


3
-rist auch eine gute Idee; Es verhindert, dass \` interpretation... (it is in your links, but its probably worth mentioning, just to round out your IFS = `(was wichtig ist, um den Verlust von Leerzeichen zu verhindern)
Peter.O

-1

Mit <<< können Sie einfach aus der Variablen lesen, die die durch Zeilenumbrüche getrennten Daten enthält:

while read -r line
do 
  echo "A line of input: $line"
done <<<"$lines"

1
Willkommen bei Unix & Linux! Dies entspricht im Wesentlichen einer Antwort von vor vier Jahren. Bitte posten Sie keine Antwort, es sei denn, Sie haben etwas Neues beizutragen.
G-Man sagt, dass Monica
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.