Mám proměnnou, která obsahuje víceřádkový výstup příkazu. Jaký je nejúčinnější způsob, jak číst výstupní řádek po řádku z proměnné?
Například:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
Můžete použít smyčku while se substitucí procesu:
while read -r line
do
echo "$line"
done < <(jobs)
Optimálním způsobem čtení víceřádkové proměnné je nastavení prázdné proměnné IFS
a proměnné printf
s koncovým novým řádkem:
# 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")
Poznámka: Pokud jde o shellcheck sc2031 , použití procesní substice je vhodnější než potrubí, aby se zabránilo [subtly] vytvoření subshell.
Také si uvědomte, že pojmenováním proměnné jobs
to může způsobit zmatek, protože to je také název společného příkazu Shell.
Zpracování výstupu příkazového řádku po řádku ( vysvětlení ):
jobs |
while IFS= read -r line; do
process "$line"
done
Pokud již máte data v proměnné:
printf %s "$foo" | …
printf %s "$foo"
Je téměř totožný s echo "$foo"
, Ale tiskne $foo
Doslova, zatímco echo "$foo"
Může interpretovat $foo
Jako možnost příkazu echo, pokud začíná -
a v některých mušlích může rozšířit sekvence zpětného lomítka v $foo
.
Všimněte si, že v některých skořápkách (popel, bash, pdksh, ale ne ksh nebo zsh) běží pravá strana potrubí v samostatném procesu, takže jakákoli proměnná nastavená ve smyčce je ztracena. Například následující skript pro počítání řádků vytiskne 0 v těchto shellech:
n=0
printf %s "$foo" |
while IFS= read -r line; do
n=$(($n + 1))
done
echo $n
Řešením je vložit zbytek skriptu (nebo alespoň část, která potřebuje hodnotu $n
Ze smyčky) do seznamu příkazů:
n=0
printf %s "$foo" | {
while IFS= read -r line; do
n=$(($n + 1))
done
echo $n
}
Pokud je jednání na neprázdných řádcích dostatečně dobré a vstup není obrovský, můžete použít rozdělení slov:
IFS='
'
set -f
for line in $(jobs); do
# process line
done
set +f
unset IFS
Vysvětlení: nastavení IFS
na jeden nový řádek způsobí, že rozdělení Wordu nastane pouze na nových řádcích (na rozdíl od jakéhokoli znaku mezery ve výchozím nastavení). set -f
Vypíná globlování (tj. Rozšíření zástupných znaků), k čemuž by jinak došlo v důsledku substituce příkazu $(jobs)
nebo proměnné substitutino $foo
. Smyčka for
působí na všechny kusy $(jobs)
, což jsou všechny neprázdné řádky ve výstupu příkazu. Nakonec obnovte nastavení globbing a IFS
.
Problém: pokud použijete while loop, bude spuštěn v subshell a všechny proměnné budou ztraceny. Řešení: použijte pro smyčku
# 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=
V nedávných verzích bash použijte mapfile
nebo readarray
pro efektivní čtení výstupů příkazů do polí
$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305
Zřeknutí se odpovědnosti: hrozný příklad, ale můžete se množit s lepším příkazem k použití, než jste sami
Pomocí <<< můžete jednoduše číst z proměnné obsahující data oddělená řádkem:
while read -r line
do
echo "A line of input: $line"
done <<<"$lines"