Diviser la sortie de la commande par saut de ligne?

9

J'ai une commande renvoyant plusieurs lignes. Pour un traitement ultérieur, je dois traiter chaque ligne unique de ces lignes.

Mon code actuel fonctionne en modifiant l'IFS ( séparateur de champ interne ):

ROWS=$(some command returning multiple lines)

O=$IFS #save original IFS
IFS=$(echo -en "\n\b") # set IFS to linebreak

for ROW in ${ROWS[@]}
do
  echo "$ROW"
done

IFS=$O #restore old IFS

Je me demande, y a-t-il un meilleur moyen d'accéder aux lignes simples de la sortie de plusieurs lignes, une sans modifier l'IFS? Surtout la lisibilité de mon script se dégrade en modifiant l'IFS.

Mise à jour: j'ai du mal à faire fonctionner les réponses, par exemple celle de choroba:

while IFS= read -r line ; do
    let var+=line #line 42
done << $(sqlite3 -list -nullvalue NULL -separator ',' /var/log/asterisk/master.db "${QUERY}")
echo "$var" # line 44

Donne moi

./bla.sh: row 44: Warning: here-document at line 43 delimited by end-of-file (wanted `$(sqlite3 -list -nullvalue NULL -separator , /var/log/asterisk/master.db ${QUERY})')
./bla.sh: row 42: let: echo "": syntax error: invalid arithmetic operator. (error causing character is \"""\").

N'importe qui peut m'aider avec ça? Merci!

stefan.at.wpf
la source
La réponse de choroba dit de faire < <(some command returning multiple lines), mais ce n'est pas ce que vous faites.
Mikel

Réponses:

13

Et readen whileboucle?

some command returning multiple lines | while IFS= read -r line ; do
    echo "$line"
done

Attention, cependant, le canal est exécuté dans un sous-shell, ce qui signifie que vous ne pouvez pas modifier les valeurs des variables pour le reste du script. Si vous avez besoin de quelque chose pour survivre à votre boucle while, vous pouvez utiliser la substitution de processus de bash:

while IFS= read -r line ; do
    let var+=line
done < <(some command returning multiple lines)
echo "$var"
choroba
la source
L'utilisation de la substitution de processus est une solution intelligente pour cela dans bash, mais il convient de noter que la portée des variables en sous-shell n'est qu'un problème dans bash, faire la même chose dans zshn'aurait pas ce problème du tout.
Caleb
ksh n'a pas ce problème non plus.
Didi Kohen,
merci les gars, j'ai essayé cette solution, mais je ne peux pas la faire fonctionner, s'il vous plaît voir mon post original mis à jour, merci!
stefan.at.wpf
@ stefan.at.wpf: Vous ne pouvez pas placer des espaces et des signes dollar à volonté. Ils ont un sens.
choroba
3

La définition IFSd'une nouvelle ligne n'est pas suffisante. (Pourquoi vous séparez-vous également des caractères de retour arrière, au fait?)

Dans votre code, ${ROWS[@]}(qui est une façon étrange d'écrire $ROWS- ROWSn'est pas un tableau) n'est pas entre guillemets. (Si c'était entre guillemets, vous obtiendriez une seule chaîne, car il ROWSne s'agit pas d'un tableau.) Ainsi, le shell divise la valeur de la variable en champs à chaque IFScaractère, puis traite chaque champ comme un modèle global. Par exemple, si l'une des lignes imprimées par la commande contient le caractère unique *, celui-ci sera remplacé par les noms de fichier dans le répertoire courant.

Vous pouvez désactiver la globalisation avec set -f. Dans la plupart des cas, lorsque vous définissez l' IFSutilisation de la fonction de fractionnement de champ du shell, vous devez également désactiver la globalisation. Remettez-le avec set +f.

L'idiome fiable pour lire ligne par ligne la sortie d'une commande est while IFS= read -r.

some command returning multiple lines |
while IFS= read -r ROW; do
  
done

Notez que la plupart des shells exécutent chaque commande d'un pipeline dans un sous-shell séparé. Donc, si vous devez définir des variables et les utiliser après la boucle, encapsulez ces commandes dans un groupe avec la boucle. (Ksh et zsh sont les exceptions, ils exécutent la dernière commande d'un pipeline dans le shell parent.)

some command returning multiple lines | {
  while IFS= read -r ROW; do
    
    row_count=$((row_count+1))
  done
  echo "There were $row_count rows."
}
Gilles 'SO- arrête d'être méchant'
la source
1

Vous mélangez la syntaxe ici-document et ici-chaîne dans votre question de mise à jour.

Soit utiliser ici-document:

while IFS= read -r line ; do
    let var+=line #line 42
done <<ENDMARK
$(sqlite3 -list -nullvalue NULL -separator ',' /var/log/asterisk/master.db "${QUERY}")
ENDMARK

Ou ici-chaîne:

while IFS= read -r line ; do
    let var+=line #line 42
done <<< $(sqlite3 -list -nullvalue NULL -separator ',' /var/log/asterisk/master.db "${QUERY}")
homme au travail
la source