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
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:
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 IFS
Variable printf
festzulegen 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 jobs
zu Verwirrung führen kann, da dies auch der Name eines allgemeinen Shell-Befehls ist.
echo
zu printf %s
, so dass Ihr Skript funktionieren würde, auch bei nicht zahm Eingang.
/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.
printf "%s\n" "$var" | while IFS= read -r line
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 $foo
buchstäblich aus, wobei dies als Option für den Echo-Befehl echo "$foo"
interpretiert werden kann, $foo
wenn er mit einem beginnt -
, und möglicherweise die Backslash-Sequenzen $foo
in 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 $n
aus 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: IFS
Wenn Sie eine einzelne Zeile als Zeilenumbruch festlegen, wird das Wort nur in Zeilenumbrüchen aufgeteilt (im Gegensatz zu Leerzeichen in der Standardeinstellung). set -f
Deaktiviert das Globbing (dh die Platzhaltererweiterung), das andernfalls bei einer Befehlsersetzung $(jobs)
oder einer Variablenersetzung auftreten würde $foo
. Die for
Schleife wirkt auf alle Teile von $(jobs)
, also alle nicht leeren Zeilen in der Befehlsausgabe. Stellen Sie abschließend die Globbing- IFS
Einstellungen wieder her.
local IFS=something
. Der Wert für den globalen Bereich wird nicht beeinflusst. IIRC unset IFS
bringt Sie nicht auf die Standardeinstellungen zurück (und funktioniert auf keinen Fall, wenn dies vorher nicht die Standardeinstellungen war).
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=
while read
Schleife 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 | while
Schleife nicht in eine Unterschale zu setzen, wie ksh es immer getan hat.)
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 durchaus einen besseren Befehl einfallen lassen als Sie
readarray
eine Funktion ein und rufen Sie die Funktion einige Male auf.
jobs="$(jobs)"
while IFS= read
do
echo $REPLY
done <<< "$jobs"
Verweise:
-r
ist 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)
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"
while IFS= read
read -r