it-swarm-eu.dev

Jak mohu číst řádek po řádku z proměnné v bashu?

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
53
Eugene Yarmash

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.

70
dogbane

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=
16
Dima
jobs="$(jobs)"
while IFS= read
do
    echo $REPLY
done <<< "$jobs"

Reference:

11
l0b0

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

10
sehe

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