Quel est le problème avec "echo $ (stuff)" ou "echo` stuff` "?

31

J'ai utilisé l'un des éléments suivants

echo $(stuff)
echo `stuff`

(où stuffest par exemple pwdou dateou quelque chose de plus compliqué).

Puis on m'a dit que cette syntaxe était erronée, une mauvaise pratique, non élégante, excessive, redondante, trop compliquée, une programmation culte du fret, noobish, naïf etc.

Mais la commande ne travail, donc ce qui est faux exactement avec elle?

Kamil Maciorowski
la source
11
Il est redondant et ne doit donc pas être utilisé. Comparez avec l'utilisation inutile de Cat: porkmail.org/era/unix/award.html
dr01
7
echo $(echo $(echo $(echo$(echo $(stuff)))))également fait le travail, et encore vous probablement pas l' utiliser, non? ;-)
Peter - Réintègre Monica
1
Qu'essayez-vous de faire avec cette expansion, exactement?
curiousguy
@curiousguy J'essaie d'aider d'autres utilisateurs, d'où ma réponse ci-dessous. Récemment, j'ai vu au moins trois questions qui utilisent cette syntaxe. Leurs auteurs essayaient de faire… divers stuff. : D
Kamil Maciorowski
2
Aussi, ne convenons-nous pas tous qu'il est imprudent de se limiter à une chambre d'écho ?? ;-)
Peter - Rétablir Monica

Réponses:

63

tl; dr

Une semelle stufffonctionnerait très probablement pour vous.



Réponse complète

Ce qui se produit

Lorsque vous exécutez foo $(stuff), voici ce qui se passe:

  1. stuff court;
  2. sa sortie (stdout), au lieu d'être imprimée, remplace $(stuff)dans l'invocation de foo;
  3. foos'exécute ensuite , ses arguments de ligne de commande dépendent évidemment de ce qui stuffest retourné.

Ce $(…)mécanisme est appelé "substitution de commande". Dans votre cas, la commande principale est celle echoqui affiche essentiellement ses arguments de ligne de commande sur stdout. Donc, tout ce qui stuffessaie d'imprimer sur stdout est capturé, passé echoet imprimé sur stdout par echo.

Si vous souhaitez que la sortie de stuffsoit imprimée sur stdout, exécutez simplement la semelle stuff.

La `…`syntaxe sert le même but que $(…)(sous le même nom: "substitution de commande"), il y a cependant peu de différences, vous ne pouvez donc pas les échanger aveuglément. Voir cette FAQ et cette question .


Dois-je éviter echo $(stuff)quoi qu'il arrive?

Il y a une raison que vous voudrez peut-être utiliser echo $(stuff)si vous savez ce que vous faites. Pour la même raison, vous devriez éviter echo $(stuff)si vous ne savez pas vraiment ce que vous faites.

Le point est stuffet echo $(stuff)n'est pas exactement équivalent. Ce dernier signifie appeler l'opérateur split + glob sur la sortie de stuffavec la valeur par défaut de $IFS. Le double guillemet de la substitution de commande empêche cela. Le simple guillemet de la substitution de commande fait qu'il ne s'agit plus d'une substitution de commande.

Pour observer cela lorsqu'il s'agit de fractionner, exécutez ces commandes:

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

Et pour le globbing:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

Comme vous pouvez le voir echo "$(stuff)"est équivalent (-ish *) à stuff. Vous pouvez l'utiliser, mais à quoi bon compliquer les choses de cette façon?

D'un autre côté, si vous souhaitez que la sortie de stuffsubisse un fractionnement + globalisation, vous pouvez trouver echo $(stuff)utile. Mais cela doit être votre décision consciente.

Il existe des commandes générant une sortie qui doivent être évaluées (ce qui inclut le fractionnement, la globalisation et plus encore) et exécutées par le shell, c'est donc eval "$(stuff)"une possibilité (voir cette réponse ). Je n'ai jamais vu une commande qui a besoin de sa sortie pour subir un fractionnement + globalisation supplémentaire avant d'être imprimée . L'utilisation délibérée echo $(stuff)semble très rare.


Et alors var=$(stuff); echo "$var"?

Bon point. Cet extrait:

var=$(stuff)
echo "$var"

devrait être équivalent à echo "$(stuff)"équivalent (-ish *) à stuff. Si c'est tout le code, lancez-le à la stuffplace.

Si, toutefois, vous devez utiliser la sortie de stuffplusieurs fois, cette approche

var=$(stuff)
foo "$var"
bar "$var"

est généralement mieux que

foo "$(stuff)"
bar "$(stuff)"

Même si fooc'est le cas echoet que vous obtenez echo "$var"votre code, il peut être préférable de le conserver de cette façon. Choses à considérer:

  1. Avec des var=$(stuff) stuffcourses une fois; même si la commande est rapide, éviter de calculer deux fois la même sortie est la bonne chose. Ou peut-êtrestuff a des effets autres que l'écriture sur stdout (par exemple la création d'un fichier temporaire, le démarrage d'un service, le démarrage d'une machine virtuelle, la notification d'un serveur distant), de sorte que vous ne voulez pas l'exécuter plusieurs fois.
  2. Si stuffgénère une sortie temporelle ou quelque peu aléatoire, vous pouvez obtenir des résultats incohérents à partir de foo "$(stuff)"et bar "$(stuff)". Après que var=$(stuff)la valeur de $varest fixée, vous pouvez être sûr foo "$var"et bar "$var"obtenir un argument de ligne de commande identique.

Dans certains cas, au lieu de foo "$var"vous pouvez vouloir (besoin) d'utiliser foo $var, en particulier si stuffgénère plusieurs arguments pour foo(une variable de tableau peut être meilleure si votre shell le prend en charge). Encore une fois, sachez ce que vous faites. En ce qui concerne echola différence entre echo $varet echo "$var"est le même qu'entre echo $(stuff)et echo "$(stuff)".


* Équivalent ( -ish )?

J'ai dit echo "$(stuff)"est équivalent (-ish) à stuff. Il y a au moins deux problèmes qui ne le rendent pas exactement équivalent:

  1. $(stuff)s'exécute stuffdans un sous-shell, il est donc préférable de dire echo "$(stuff)"est équivalent (-ish) à (stuff). Les commandes qui affectent le shell dans lequel elles s'exécutent, si elles sont dans un sous-shell, n'affectent pas le shell principal.

    Dans cet exemple, stuffvoici a=1; echo "$a":

    a=0
    echo "$(a=1; echo "$a")"      # echo "$(stuff)"
    echo "$a"
    

    Comparez-le avec

    a=0
    a=1; echo "$a"                # stuff
    echo "$a"
    

    et avec

    a=0
    (a=1; echo "$a")              # (stuff)
    echo "$a"
    

    Un autre exemple, commencez par stuffêtre cd /; pwd:

    cd /bin
    echo "$(cd /; pwd)"           # echo "$(stuff)"
    pwd
    

    et test stuffet (stuff)versions.

  2. echon'est pas un bon outil pour afficher des données non contrôlées . Ce dont echo "$var"nous parlions aurait dû être printf '%s\n' "$var". Mais comme la question mentionne echoet que la solution la plus probable est de ne pas l'utiliser echoen premier lieu, j'ai décidé de ne pas introduire printfjusqu'à présent.

  3. stuffou (stuff)entrelacera les sorties stdout et stderr, tandis que echo $(stuff)toutes les sorties stderr de stuff(qui s'exécute en premier) seront imprimées , et alors seulement la sortie stdout digérée par echo(qui s'exécutera en dernier).

  4. $(…)supprime toute nouvelle ligne de fin, puis l' echoajoute en arrière. echo "$(printf %s 'a')" | xxdDonne donc une sortie différente de printf %s 'a' | xxd.

  5. Certaines commandes ( lspar exemple) fonctionnent différemment selon que la sortie standard est une console ou non; il ls | catn'en va pas de même ls. De même echo $(ls)fonctionnera différemment que ls.

    Mis à lspart, dans un cas général, si vous devez forcer cet autre comportement, stuff | catc'est mieux que echo $(ls)ou echo "$(ls)"parce qu'il ne déclenche pas tous les autres problèmes mentionnés ici.

  6. Peut-être un statut de sortie différent (mentionné pour l'exhaustivité de cette réponse wiki; pour plus de détails, voir une autre réponse qui mérite le crédit).

Kamil Maciorowski
la source
5
Une autre différence: le stuffchemin entrelacera stdoutet sortira stderr, tandis que echo `stuff` toutes les stderrsorties seront imprimées stuffet seulement ensuite la stdoutsortie.
Ruslan
3
Et ceci est un autre exemple pour: Il ne peut guère y avoir trop de citations dans les scripts bash. echo $(stuff)peut vous poser bien plus de problèmes comme echo "$(stuff)"si vous ne vouliez pas explicitement la globalisation et l'expansion.
rexkogitans
2
Une autre légère différence: $(…)supprime toute nouvelle ligne de fin, puis l' echoajoute à nouveau. echo "$(printf %s 'a')" | xxdDonne donc une sortie différente de printf %s 'a' | xxd.
derobert
1
N'oubliez pas que certaines commandes ( lspar exemple) fonctionnent différemment selon que la sortie standard est une console ou non; il ls | catn'en va pas de même ls. Et bien sûr echo $(ls), fonctionnera différemment que ls.
Martin Rosenau
@Ruslan La réponse est maintenant un wiki communautaire. J'ai ajouté votre commentaire utile au wiki. Merci.
Kamil Maciorowski
5

Autre différence: le code de sortie du sous-shell est perdu, donc le code de sortie de echoest récupéré à la place.

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0
WaffleSouffle
la source
3
Bienvenue sur Super User! Pouvez-vous expliquer la différence que vous démontrez? Merci
bertieb
4
Je crois que cela montre que le code retour $?sera le code retour de echo(pour echo $(stuff)) au lieu de celui returnédité par stuff. La stufffonction return 1(code de sortie), mais la echodéfinit sur "0" quel que soit le code retour de stuff. Devrait encore être dans la réponse.
Ismael Miguel
la valeur de retour du sous-shell a été perdue
phuclv