Pourquoi la substitution de commandes shell engloutit-elle un caractère de fin de ligne?

25

Selon l'exemple suivant, et comme dans ma récente question Dans bash, où est passé le caractère de fin de ligne? , Je veux savoir "pourquoi" ça arrive

  x="$(echo -ne "a\nb\n")" ; echo -n "$x" | xxd -p 
# Output is: 610a62 
# The trailing newline from the 'echo' command
#   has been "deleted" by Command Substitution

Je suppose qu'il doit y avoir une raison très importante pour une action shell, à savoir la substitution de commande, de supprimer réellement certaines données de la sortie de commande qu'elle remplace ...
mais je ne peux pas me concentrer sur celle-ci, car il semble que ce soit l'antithèse de ce qu'il est censé faire .. ie. pour passer la sortie d'une commande dans le processus de script ... Retenir un caractère me semble bizarre, mais je suppose qu'il y a une raison raisonnable à cela ... Je suis impatient de savoir quelle est cette raison .. .

Peter.O
la source

Réponses:

22

Parce que le shell n'était pas à l'origine destiné à être un langage de programmation complet.

Il est assez difficile de supprimer une fin \nd'une sortie de commande. Cependant, à des fins d'affichage, presque toutes les commandes terminent leur sortie par \n, donc ... il doit y avoir un moyen simple de le supprimer lorsque vous souhaitez l'utiliser dans une autre commande. Le retrait automatique avec la $()construction a été la solution choisie.

Alors, vous accepterez peut-être cette question comme réponse:

Pouvez-vous trouver un moyen simple de supprimer la fin \nsi cela n'a pas été fait automatiquement dans la commande suivante?

> echo The current date is "$(date)", have a good day!

Notez que les guillemets sont nécessaires pour éviter de casser les espaces doubles qui peuvent apparaître dans les dates formatées.

Stéphane Gimenez
la source
1
Si ce que vous dites est vrai, je serais absolument stupéfait. S'il y a un shoptpour changer votre comportement par défaut décrit, alors je serais à nouveau heureux ... Je trouve difficile de penser que Bash / Linux / Unix polluerait une fonction aussi critique que "capturer stdout" juste parce qu'elle daten'a pas une option avec / sans '\ n' ... La solution évidente serait que bash (ou un autre shell) ait un shoptpour cela ... En connaissez-vous? .. et avez-vous des liens de référence qui parlent du pourquoi et du pourquoi de ce problème? ... et pourquoi n'a pas dated'option \ n .. Ça devrait!
Peter.O
2
Les substitutions de commandes sont apparues dans la première version du shell Bourne dans la "7ème édition" d'Unix en 1979. C'était déjà une grande révolution et je suppose (je ne suis pas né) que personne ne se souciait de continuer à traîner '\ n' ou pas. Oui, vous pouvez lire la page de manuel en moins de 10 minutes, et il semble que rien n'ait changé jusqu'à présent concernant la substitution de commandes. À l'exception de la $()syntaxe alternative préférée .
Stéphane Gimenez
Merci .. Je pensais que cela pourrait être un problème hérité, et cela se résume certainement au fait que je ferais mieux de commencer à regarder mes substitutions de commande plus attentivement. Jusqu'à aujourd'hui, je dois avoir survécu sur une aile et une prière .. Certains de ces "événements mystérieux" que j'ai rencontrés commencent à avoir un sens maintenant :) ... Donc, dans le cas inverse de votre "rendez-vous", si Je veux garder ma trace \ n, je dois coder quelque chose comme ceci :( { echo -n "The current date is "; var="$(date; echo -e x)"; var="${var%x}"; echo -n "$var"; echo ", have a good day!"; }.. alors qu'il soit :)
Peter.O
PS concernant mon commentaire ci-dessus: Bien sûr, datecela fonctionnerait, mais je viens de l'utiliser datecomme exemple de toute commande ..
Peter.O
Votre `` question '' sur la date a été la plus forte incitation à ma compréhension de `` pourquoi '' la Substitution de Commandement fait ce qu'elle fait, donc cela a été la `` meilleure '' réponse à ma question ... et j'avais certainement aussi besoin des autres bonnes réponses ..
Peter.O
19

Cela fait partie de la norme :

Le shell doit étendre la substitution de commande en exécutant la commande dans un environnement de sous-shell (voir Environnement d'exécution du shell ) et en remplaçant la substitution de commande (le texte de la commande plus le "$ ()" ou les guillemets) qui l'entourent par la sortie standard de la commande, en supprimant séquences d'un ou plusieurs <newline> s à la fin de la substitution.

Bien sûr, la norme est probablement écrite de cette façon parce que c'est comme kshça ou quelque chose, mais c'est la norme, et c'est un comportement documenté. Si vous ne l'aimez pas, utilisez Perl ou autre chose qui vous permettra de conserver les nouvelles lignes de fin.

Steven Pritchard
la source
Un joli résumé .. Merci .. et les liens ont certainement aidé
Peter.O
4
La norme est comme ça parce que c'est ainsi que le shell Bourne l'a fait et tous les autres shell depuis.
Gilles 'SO- arrête d'être méchant'
6

Eh bien, cela a du sens pour moi. La nouvelle ligne n'est là qu'en premier lieu, dans la sortie de commande normale, de sorte que l'invite s'affiche une fois la commande terminée sur une nouvelle ligne. La nouvelle ligne ne fait pas partie de la sortie d'origine dans la plupart des cas, elle est là pour ranger l'écran. Lorsque vous analysez la sortie d'une commande, la nouvelle ligne à la fin est généralement gênante. Pourquoi la wccommande génère-t-elle 2 lignes de texte? Oh, ce n'est pas le cas, il en génère un suivi d'une nouvelle ligne. Lorsque vous analysez, wcvous ne voulez pas vous inquiéter du fait qu'il y a 2 lignes de sortie - il n'y en a pas vraiment, il n'y en a qu'une.

EightBitTony
la source
@EightBitTony: Ma question n'est en aucun cas liée à l'affichage de quelque chose dans le terminal. C'est assez simple. S'il y a une nouvelle ligne à afficher, alors elle affichera la nouvelle ligne. Le point en question ici, c'est qu'un \ndans le stdoutflux d' origine est supprimé par la substitution de commande. c'est à dire. mes nouvelles données (données très importantes) ont des retours à la ligne supprimés, même lorsque les données sont entourées de "guillemets". Je comprends raisonnablement comment et pourquoi le fractionnement de mots fonctionne, mais je ne sais absolument pas pourquoi la substitution de commandes supprime uniquement les nouvelles lignes et les seules lignes de fin .
Peter.O
1
Rien de ce que vous avez dit ne change mon opinion. Plus souvent qu'autrement, la nouvelle ligne finale dans n'importe quelle sortie est là uniquement à des fins d'affichage, pas dans le cadre des données.
EightBitTony
Je suis d'accord avec votre point de vue, et je l'ai toujours fait ... Oui, la nouvelle ligne à la fin de la sortie est pour plus de commodité ... mais mon problème n'a rien à voir avec cela ... Je demande: pourquoi la substitution de commandes la supprime-t-elle de force ? ... Ce sont deux problèmes différents. Ma question devrait peut-être être la suivante: existe-t-il une solution de contournement intégrée? . car devoir coder pour ce problème en ajoutant un caractère factice de fin, c'est nul! ... Je vais le faire si je le dois, mais c'est la première fois que je suis bloqué par bash (et je suis en état de choc :) .. Ça fait tellement bien ...
Peter.O
Comme mentionné dans une autre réponse, cela fait partie de la norme. Pour moi, je pense que c'est comme ça parce que lorsque vous post-traitez des données, vous voulez seulement voir les données, pas la nouvelle ligne pratique. C'est parce que le shell attend de vous que vous gériez les données dans un tube et que la nouvelle ligne ne soit pas des données. Plus et nous devons porter cela à une discussion.
EightBitTony
Merci .. J'ai assez bien compris maintenant .. Il semble que ce soit un cas de substitution de commande qui vise simplement à supprimer les nouvelles lignes au lieu de les maintenir .. Il doit y avoir une certaine sagesse dans ce mouvement que je n'ai pas ressenti " "pour l'instant, mais je pensais que l'utilisation écrasante de toutes les lettres minuscules dans les noms de fichiers était un peu étrange / inutile, mais je pensais juste l'autre jour qu'il s'agissait en fait d'un système assez confortable. Je vais probablement voir (plus profondément) la rime et la raison du comportement de Substitution de Commandement, un jour ...
Peter.O
1

Je rencontrais le même problème et j'ai trouvé l'exemple ci-dessous. Il semble que la citation ait aidé la situation, du moins pour moi:

http://tldp.org/LDP/abs/html/commandsub.html .

dir_listing=`ls -l`
echo $dir_listing     # unquoted

# Expecting a nicely ordered directory listing.

# However, what you get is:
# total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo
# bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh

# The newlines disappeared.


echo "$dir_listing"   # quoted
# -rw-rw-r--    1 bozo       30 May 13 17:15 1.txt
# -rw-rw-r--    1 bozo       51 May 15 20:57 t2.sh
# -rwxr-xr-x    1 bozo      217 Mar  5 21:13 wi.sh
christian_hercules
la source
2
Voici ce qui se passe ici. Disons que vous avez une variable $str, contenant la chaîne "a b c". Si vous passez $strà echosans les guillemets ( echo $str), echorecevra a, bet cque trois arguments distincts. Votre shell ne se soucie pas de l'espace entre les arguments. echoaffichera ensuite ces arguments avec des espaces séparant chacun, donc vous obtenez "a b c". Si vous passez la variable à echoavec des guillemets autour d'elle ( echo "$str"), le shell la voit comme un argument et la echoreçoit en tant que telle, donc le blanc n'est pas réduit. Il en va de même pour les nouvelles lignes.
1
Le problème que pose l'affiche originale est que les nouvelles lignes de fin (juste les toutes dernières) de ses commandes disparaissent. À moins que le -ndrapeau ne soit passé , echoajoute une nouvelle ligne de fin à sa sortie, donc les deux échos de votre exemple ont des nouvelles lignes de fin. Cependant, si vous essayez echo -n $dir_listingou echo -n "$dir_listing", vous verrez qu'aucun retour à la ligne de fin n'est imprimé avec ou sans guillemets $dir_listing, ce qui suggère que le problème vient de la substitution de commande.
2
tldp est un endroit très obsolète pour en savoir plus sur les scripts shell. Essayez plutôt le wiki Wooledge Bash. mywiki.wooledge.org/BashGuide
Wildcard
-1

pourquoi echo "hello "(sans les guillemets) engloutit l'espace? la valeur des caractères IFS est vue par le shell comme étant des délimiteurs, donc les derniers (qui ne délimitent rien) sont supprimés. la substitution de commend consiste simplement à exécuter les commends dans un sous-shell, elle suit donc la même logique.

Philomath
la source
@Philomath: Les deux x="hello  "et x="hello     "perdront tous leurs espaces de fin s'ils sont affichés via echo $x... Cependant, seule la toute dernière nouvelle ligne de la Substitution de commande est perdue.
Peter.O
étrange, je ne vois aucune différence dans mon bash (vérification avec xxd)
Philomath
Je vérifie juste cela ... Cela supprime tous les sauts de ligne en fin de ligne ... J'expérimentais avec IFS quand j'ai eu cette impression, alors annulons celle-là :) .. mais la question reste toujours ... C'est une partie fondamentale fractionnement de mots pour condenser plusieurs espaces en un seul espace et pour supprimer totalement les espaces de début et de fin. Je peux comprendre cela, mais je ne comprends certainement pas pourquoi, avec la substitution de commandes, il ne supprime que \ n et pas tous les espaces (comme avec décomposition des mots), et pourquoi seulement la fin? ... et surtout: "Substitution de commandes" ne se substitue que partiellement .. Pourquoi?
Peter.O
4
IFSn'a rien à voir avec la suppression des sauts de ligne à la fin des substitutions de commande.
Gilles 'SO- arrête d'être méchant'