Capture de plusieurs sorties de ligne dans une variable Bash

583

J'ai un script «myscript» qui produit les éléments suivants:

abc
def
ghi

dans un autre script, j'appelle:

declare RESULT=$(./myscript)

et $RESULTobtient la valeur

abc def ghi

Existe-t-il un moyen de stocker le résultat soit avec les sauts de ligne, soit avec le caractère '\ n' pour que je puisse le sortir avec ' echo -e'?

Parker
la source
1
ça me surprend. vous n'avez pas $ (cat ./myscipt)? sinon je m'attendais à ce qu'il essaie d'exécuter les commandes abc, def et ghi
Johannes Schaub - litb
@litb: oui, je suppose que oui; vous pouvez également utiliser $ (<./ myscript) qui évite d'exécuter une commande.
Jonathan Leffler
2
(NB: les deux commentaires ci-dessus font référence à une révision de la question qui a commencé. J'ai un script 'myscript' qui contient les éléments suivants , ce qui a conduit aux questions. La révision actuelle de la question ( j'ai un script ' myscript "qui génère ce qui suit ) rend les commentaires superflus. Cependant, la révision est du 2011-11-11, longtemps après que les deux commentaires ont été faits.
Jonathan Leffler

Réponses:

1083

En fait, RESULT contient ce que vous voulez - pour démontrer:

echo "$RESULT"

Ce que vous montrez, c'est ce que vous obtenez:

echo $RESULT

Comme indiqué dans les commentaires, la différence est que (1) la version entre guillemets doubles de la variable ( echo "$RESULT") préserve l'espacement interne de la valeur exactement telle qu'elle est représentée dans la variable - nouvelles lignes, tabulations, espaces vides et tout - alors que (2 ) la version sans guillemets ( echo $RESULT) remplace chaque séquence d'un ou plusieurs blancs, tabulations et sauts de ligne par un seul espace. Ainsi, (1) préserve la forme de la variable d'entrée, tandis que (2) crée une seule ligne de sortie potentiellement très longue avec des «mots» séparés par des espaces simples (où un «mot» est une séquence de caractères non blancs; il n'est pas nécessaire 't être alphanumériques dans aucun des mots).

Jonathan Leffler
la source
69
@troelskn: la différence est que (1) la version entre guillemets de la variable préserve l'espacement interne de la valeur exactement telle qu'elle est représentée dans la variable, les nouvelles lignes, les tabulations, les espaces vides et tout, tandis que (2) la version sans guillemets remplace chaque séquence d'un ou plusieurs espaces, tabulations et sauts de ligne avec un seul espace. Ainsi, (1) préserve la forme de la variable d'entrée, tandis que (2) crée une seule ligne de sortie potentiellement très longue avec des «mots» séparés par des espaces simples (où un «mot» est une séquence de caractères non blancs; il n'est pas nécessaire 't être alphanumériques dans aucun des mots).
Jonathan Leffler
23
Pour rendre la réponse plus facile à comprendre: la réponse indique que l'écho "$ RESULT" conserve la nouvelle ligne, contrairement à l'écho $ RESULT.
Yu Shen
Cela ne parvient pas à préserver les nouvelles lignes et les espaces de tête dans certaines situations.
CommaToast
3
@CommaToast: Allez-vous développer cela? Les retours à la ligne de fin sont perdus; il n'y a pas de moyen facile de contourner cela. Blancs en tête - Je ne suis au courant d'aucune circonstance dans laquelle ils sont perdus.
Jonathan Leffler
90

Un autre écueil avec cela est que la substitution de commandes - $()- supprime les nouvelles lignes. Probablement pas toujours important, mais si vous voulez vraiment conserver exactement ce qui a été sorti, vous devrez utiliser une autre ligne et quelques citations:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

Ceci est particulièrement important si vous souhaitez gérer tous les noms de fichiers possibles (pour éviter un comportement indéfini comme opérer sur le mauvais fichier).

l0b0
la source
4
J'ai dû travailler pendant un certain temps avec un shell cassé qui n'a pas supprimé la dernière nouvelle ligne de la substitution de commandes (ce n'est pas une substitution de processus ), et cela a presque tout cassé. Par exemple, si vous l'avez fait pwd=`pwd`; ls $pwd/$file, vous avez obtenu un retour à la ligne avant le /, et mettre le nom entre guillemets n'a pas aidé. Il a été corrigé rapidement. C'était en 1983-5 sur ICL Perq PNX; le shell n'avait pas $PWDde variable intégrée.
Jonathan Leffler
19

Dans le cas où vous êtes intéressé par des lignes spécifiques, utilisez un tableau de résultats:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"
user2574210
la source
3
S'il y a des espaces dans les lignes, cela comptera les champs (contenu entre les espaces) plutôt que les lignes.
Liam
2
Vous utiliseriez readarrayet substitution au lieu de substitution de commande de processus: readarray -t RESULT < <(./myscript>.
chepner
15

En plus de la réponse donnée par @ l0b0, je venais d'avoir la situation où je devais à la fois conserver toutes les nouvelles lignes en fin de sortie du script et vérifier le code retour du script. Et le problème avec la réponse de l0b0 est que «l'écho x» réinitialisait $? retour à zéro ... j'ai donc réussi à trouver cette solution très astucieuse:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"
Lurchman
la source
C'est joli, j'ai en fait eu le cas d'utilisation où je veux avoir une die ()fonction qui accepte un code de sortie arbitraire et éventuellement un message, et je voulais utiliser une autre usage ()fonction pour fournir le message mais les sauts de ligne continuaient à être écrasés, j'espère que cela me permet contourner cela.
dragon788
1

Après avoir essayé la plupart des solutions ici, la chose la plus simple que j'ai trouvée était évidente - en utilisant un fichier temporaire. Je ne suis pas sûr de ce que vous voulez faire avec la sortie de plusieurs lignes, mais vous pouvez ensuite traiter cela ligne par ligne en utilisant la lecture. La seule chose que vous ne pouvez pas vraiment faire est de tout coller facilement dans la même variable, mais pour la plupart des raisons pratiques, c'est beaucoup plus facile à gérer.

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

Hack rapide pour lui faire faire l'action demandée:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

Notez que cela ajoute une ligne supplémentaire. Si vous travaillez dessus, vous pouvez le coder, je suis juste trop paresseux.


EDIT: Bien que ce cas fonctionne parfaitement, les personnes qui lisent ceci doivent savoir que vous pouvez facilement écraser votre stdin à l'intérieur de la boucle while, vous donnant ainsi un script qui exécutera une ligne, effacera stdin et quittera. Comme ssh fera ça je pense? Je viens de le voir récemment, d'autres exemples de code ici: /unix/24260/reading-lines-from-a-file-with-bash-for-vs- while

Encore une fois! Cette fois avec un descripteur de fichier différent (stdin, stdout, stderr valent 0-2, nous pouvons donc utiliser & 3 ou plus dans bash).

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

vous pouvez également utiliser mktemp, mais ce n'est qu'un exemple de code rapide. L'utilisation de mktemp ressemble à:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

Utilisez ensuite $ filenamevar comme vous le feriez pour le nom réel d'un fichier. Il n'a probablement pas besoin d'être expliqué ici, mais quelqu'un s'est plaint dans les commentaires.

user1279741
la source
J'ai essayé d'autres solutions aussi, avec votre première suggestion, j'ai finalement réussi à faire fonctionner mon script
Kar.ma
1
Downvote: Ceci est excessivement complexe et ne parvient pas à éviter plusieurs bashpièges courants .
tripleee
Ya quelqu'un m'a parlé du problème étrange de la poignée de fichier stdin l'autre jour et j'étais comme "wow". Laisse moi ajouter quelque chose très rapidement.
user1279741
Dans Bash, vous pouvez utiliser l' readextension de commande -u 3pour lire à partir du descripteur de fichier 3.
Jonathan Leffler
Pourquoi pas pendant ... faire ... fait <<(. Myscript.sh)
jtgd
0

Que diriez-vous de cela, il lira chaque ligne dans une variable et qui peut être utilisée par la suite! dire que la sortie de myscript est redirigée vers un fichier appelé myscript_output

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'
Rahul Reddy
la source
4
Eh bien, ce n'est pas bash, c'est génial.
vadipp