it-swarm.com.de

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

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

Zum Beispiel:

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

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

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

Eine optimale Möglichkeit zum Lesen einer mehrzeiligen Variablen besteht darin, eine leere Variable IFS und printf die Variable mit einem nachgestellten Zeilenumbruch einzufügen:

# 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 einer Prozesssubstitution einer Pipe vorzuziehen, um das [subtile] Erstellen einer Subshell zu vermeiden.

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

70
dogbane

So verarbeiten Sie die Ausgabe einer Befehlszeile zeilenweise ( Erklärung ):

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 $foo Wörtlich aus, während echo "$foo"$foo Als Option für den Echo-Befehl interpretieren könnte, wenn Es beginnt mit einem - und kann in einigen Shells Backslash-Sequenzen in $foo erweitern.

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 zum Zählen von Zeilen 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 $n Aus der Schleife benötigt) in eine Befehlsliste aufzunehmen:

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

Wenn es gut genug ist, auf nicht leere Zeilen zu reagieren, und die Eingabe nicht groß ist, können Sie die Wortaufteilung verwenden:

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

Erläuterung: Wenn Sie IFS auf eine einzelne neue Zeile setzen, erfolgt die Word-Aufteilung nur in neuen Zeilen (im Gegensatz zu Leerzeichen unter der Standardeinstellung). set -f Deaktiviert das Globbing (d. H. Platzhaltererweiterung), was andernfalls beim Ergebnis einer Befehlssubstitution $(jobs) oder eines variablen Substituts $foo Der Fall wäre. Die for -Schleife wirkt auf alle Teile von $(jobs), die alle nicht leeren Zeilen in der Befehlsausgabe sind. Stellen Sie abschließend die Einstellungen für Globbing und IFS wieder her.

Problem: Wenn Sie die while-Schleife verwenden, wird sie 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=
16
Dima
jobs="$(jobs)"
while IFS= read
do
    echo $REPLY
done <<< "$jobs"

Verweise:

11
l0b0

Verwenden Sie in neueren Bash-Versionen mapfile oder 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 häufig einen besseren Befehl einfallen lassen als Sie selbst

10
sehe

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"
0