it-swarm.it

Come posso leggere riga per riga da una variabile in bash?

Ho una variabile che contiene l'output multilinea di un comando. Qual è il modo più efficiente per leggere l'output riga per riga dalla variabile?

Per esempio:

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

Puoi usare un ciclo while con la sostituzione del processo:

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

Un modo ottimale per leggere una variabile multilinea è impostare una variabile IFS vuota e printf la variabile con una nuova riga finale:

# 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")

Nota: Come per shellcheck sc2031 , l'uso della sottostizione di processo è preferibile a una pipe per evitare [sottilmente] di creare una subshell.

Inoltre, tieni presente che nominando la variabile jobs può causare confusione poiché è anche il nome di un comando Shell comune.

70
dogbane

Per elaborare l'output di una riga di comando per riga ( spiegazione ):

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

Se hai già i dati in una variabile:

printf %s "$foo" | …

printf %s "$foo" È quasi identico a echo "$foo", Ma stampa letteralmente $foo, Mentre echo "$foo" Potrebbe interpretare $foo Come un'opzione per il comando echo se inizia con un - e potrebbe espandere le sequenze di barre rovesciate in $foo in alcune shell.

Si noti che in alcune shell (ash, bash, pdksh, ma non ksh o zsh), il lato destro di una pipeline viene eseguito in un processo separato, quindi qualsiasi variabile impostata nel loop viene persa. Ad esempio, il seguente script di conteggio delle righe stampa 0 in queste shell:

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

Una soluzione alternativa consiste nel mettere il resto dello script (o almeno la parte che richiede il valore di $n Dal ciclo) in un elenco di comandi:

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

Se agire sulle righe non vuote è abbastanza buono e l'input non è enorme, puoi usare la suddivisione in Word:

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

Spiegazione: l'impostazione di IFS su una singola nuova riga fa sì che la divisione delle parole avvenga solo nelle nuove righe (al contrario di qualsiasi carattere di spazio vuoto nell'impostazione predefinita). set -f Disattiva il globbing (ovvero l'espansione dei caratteri jolly), che altrimenti si verificherebbe al risultato di una sostituzione di comando $(jobs) o di una sostituzione sostitutiva $foo. Il ciclo for agisce su tutti i pezzi di $(jobs), che sono tutte le righe non vuote nell'output del comando. Infine, ripristina le impostazioni globbing e IFS.

Problema: se si utilizza while loop verrà eseguito in subshell e tutte le variabili andranno perse. Soluzione: utilizzare per loop

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

Riferimenti:

11
l0b0

Nelle versioni bash recenti, utilizzare mapfile o readarray per leggere in modo efficiente l'output del comando negli array

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

Disclaimer: esempio orribile, ma puoi prontamente inventare un comando migliore da usare di te stesso

10
sehe

Puoi usare <<< per leggere semplicemente dalla variabile contenente i dati separati da una nuova riga:

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