Où est passé le caractère de fin de ligne de ma substitution de commande?

15

Le code suivant décrit le mieux la situation. Pourquoi la dernière ligne ne génère-t-elle pas le caractère de retour à la ligne de fin? La sortie de chaque ligne est indiquée dans le commentaire. J'utilise GNU bash, version 4.1.5

     echo -n $'a\nb\n'                  | xxd -p  # 610a620a  
           x=$'a\nb\n'   ; echo -n "$x" | xxd -p  # 610a620a
     echo -ne "a\nb\n"                  | xxd -p  # 610a620a
x="$(echo -ne "a\nb\n")" ; echo -n "$x" | xxd -p  # 610a62
Peter.O
la source
4
Solution pour les rares fois où c'est nécessaire:tmp=$(somecommand; echo a); tmp=${tmp%a}
Gilles 'SO- arrête d'être maléfique'
1
@Gilles: Re votre exemple, ci-dessus: tmp=$(somecommand; echo a)... Cela a certainement ramené le point à la maison ... Jusqu'à ce que je voie l'exemple, ma tendance aurait toujours été d'utiliser echo -n a... mais, bien sûr !, il n'est pas nécessaire de le -n, car la substitution de commande supprimera dans tous les cas la nouvelle ligne de fin introduite! ... merci ...
Peter.O
stackoverflow.com/questions/613572/…
sancho.s Rétablir Monica

Réponses:

18

La fonction de substitution de commandes $()(et son cousin le backtick) supprime spécifiquement les sauts de ligne de fin. Il s'agit du comportement documenté , et vous devez toujours en être conscient lorsque vous utilisez la construction.

Les sauts de ligne à l'intérieur du corps du texte ne sont pas supprimés par l'opérateur de substitution, mais ils peuvent également être supprimés lors du fractionnement de mots sur le shell, donc la façon dont cela se produit dépend de l'utilisation ou non de guillemets. Notez la différence entre ces deux utilisations:

$ echo -n "$(echo -n 'a\nb')"
a
b

$ !! | xxd -p
610a62

$ echo -n  $(echo -n 'a\nb')
a b

$ !! | xxd -p   
612062

Dans le deuxième exemple, la sortie n'a pas été citée et la nouvelle ligne a été interprétée comme une séparation de mots, la faisant apparaître dans la sortie comme un espace!

Caleb
la source
1
Merci Caleb .. Je savais de changer mot-division des espaces en un seul espace où unquoted ... Voilà pourquoi je suis plus surpris de voir mon retour à la ligne de fuite disparaître , même si je l' avais cité ... Maintenant , je suis conscient que c'est à cause du comportement «normal» de la substitution de commande laissant tomber une nouvelle ligne de fin ... Oh bien, c'est la vie .. et merci pour le lien
Peter.O
6

Lors de l'utilisation de la substitution de commandes, le shell exécute les commandes dans un sous-shell, renvoyant leur sortie standard. dans ce processus, les caractères IFS perdent leur signification (s'ils ne sont pas entre guillemets), car la recommandation renvoie des mots simples, donc les derniers sont supprimés. Par exemple:

$ echo "$(echo -e '\n')" | wc
 1       0       1

$ echo -e '\n' | wc
2       0       2

et plus concrètement, pwdfonctionnera même si votre nom de répertoire a une nouvelle ligne entre les deux, mais $(pwd)ne le fera pas.

La solution de contournement habituelle consiste à ajouter quelque chose à la fin de votre commande et à la supprimer par la suite.

Philomath
la source
1
Merci Philomath ... en passant, vous premier exemple est syntaxiquement mal ( « wc » n'accepte pas une chaîne comme arg) .. Pour vous des exemples à donner un sens, le premier pourrait être echo "$(echo -e '\n')" | wc, qui produit 1   0   1, par rapport à la2   0   2
Peter.O
@fred: Oups, juste une faute de frappe.
Philomath
1
"converti en espaces" n'est pas l'explication appropriée, il y a juste un certain fractionnement impliqué. En particulier, vous pouvez supprimer de l'espace IFS et ceux-ci n'agiront pas comme des séparateurs. De plus, votre exemple tombe dans une catégorie de cas spéciale, et il y a une différence entre $(pwd)et "$(pwd)", voir la réponse de Caleb.
Stéphane Gimenez
PS ... Votre suggestion de la solution de contournement habituelle est exactement ce dont j'avais besoin pour éclaircir celui-ci ..
Peter.O
Re "converti en espaces", voir Word Splitting
Peter.O