web-dev-qa-db-fra.com

Comment lire ligne par ligne dans une variable en bash?

J'ai une variable qui contient la sortie multiligne d'une commande. Quelle est la manière la plus efficace de lire la sortie ligne par ligne à partir de la variable?

Par exemple:

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

Vous pouvez utiliser une boucle while avec substitution de processus:

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

Une manière optimale de lire une variable multiligne consiste à définir une variable IFS vide et printf la variable avec une nouvelle ligne de fin:

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

Remarque: Selon shellcheck sc2031 , l'utilisation de la sous-station de processus est préférable à un tube pour éviter de créer [subtilement] une sous-coque.

Sachez également qu'en nommant la variable jobs, cela peut provoquer de la confusion car il s'agit également du nom d'une commande Shell courante.

70
dogbane

Pour traiter la sortie d'une ligne de commande ligne par ligne ( explication ):

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

Si vous avez déjà les données dans une variable:

printf %s "$foo" | …

printf %s "$foo" Est presque identique à echo "$foo", Mais imprime $foo Littéralement, tandis que echo "$foo" Peut interpréter $foo Comme une option de la commande echo si il commence par un - et peut étendre les séquences de barre oblique inverse dans $foo dans certains shells.

Notez que dans certains shells (ash, bash, pdksh, mais pas ksh ou zsh), le côté droit d'un pipeline s'exécute dans un processus distinct, donc toute variable que vous définissez dans la boucle est perdue. Par exemple, le script de comptage de lignes suivant affiche 0 dans ces shells:

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

Une solution de contournement consiste à mettre le reste du script (ou au moins la partie qui a besoin de la valeur de $n Dans la boucle) dans une liste de commandes:

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

Si agir sur les lignes non vides est suffisant et que l'entrée n'est pas énorme, vous pouvez utiliser le fractionnement de Word:

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

Explication: si vous définissez IFS sur une seule nouvelle ligne, le fractionnement de Word ne se produit que sur les nouvelles lignes (contrairement à tout caractère d'espacement sous le paramètre par défaut). set -f Désactive la globalisation (c'est-à-dire l'expansion des caractères génériques), ce qui autrement se produirait au résultat d'une substitution de commande $(jobs) ou d'une variable substitutino $foo. La boucle for agit sur toutes les parties de $(jobs), qui sont toutes les lignes non vides dans la sortie de la commande. Enfin, restaurez les paramètres globbing et IFS.

Problème: si vous utilisez la boucle while, elle s'exécutera en sous-shell et toutes les variables seront perdues. Solution: utiliser pour la boucle

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

Références:

11
l0b0

Dans les versions bash récentes, utilisez mapfile ou readarray pour lire efficacement la sortie des commandes dans les tableaux

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

Avertissement: horrible exemple, mais vous pouvez toujours trouver une meilleure commande à utiliser que vous-même

10
sehe

Vous pouvez utiliser <<< pour lire simplement à partir de la variable contenant les données séparées par des sauts de ligne:

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