Quel est l'avantage d'utiliser $ () au lieu de backticks dans les scripts shell?

175

Il existe deux façons de capturer la sortie de la ligne de commande dans bash:

  1. Anciens backticks du shell Bourne ``:

    var=`command`
  2. $() syntaxe (qui, pour autant que je sache, est spécifique à Bash, ou du moins n'est pas prise en charge par les anciens shells non POSIX comme Bourne d'origine)

    var=$(command)

Y a-t-il un avantage à utiliser la deuxième syntaxe par rapport aux backticks? Ou les deux sont-ils totalement équivalents à 100%?

DVK
la source
15
$()est POSIX et supporté par tous les shells Bourne modernes, par exemple ksh, bash, ash, dash, zsh, busybox, vous l'appelez. (Un pas si moderne est Solaris /bin/sh, mais sur Solaris, vous vous assurez d'utiliser le moderne à la /usr/xpg4/bin/shplace).
Jens
27
Aussi, une note sur l'utilisation $()et les backticks dans les alias. Si vous avez alias foo=$(command)dans votre .bashrcalors commandsera exécuté lorsque la commande d'alias elle-même sera exécutée pendant l' .bashrcinterprétation. Avec alias foo=`command`, commandsera exécuté à chaque exécution de l'alias. Mais si vous échappez au $avec le $()formulaire (par exemple alias foo=\$(command)), il s'exécutera également à chaque fois que l'alias est exécuté, au lieu de lors de l' .bashrcinterprétation. Autant que je sache en testant, de toute façon; Je ne trouve rien dans la documentation bash qui explique ce comportement.
dirtside
4
@dirtside Quel shell est-ce, j'ai testé le shell bash et POSIX, le backtick est exécuté lorsque je source. Exemple simple: alias curDate = `date` Après l'avoir source et exécuté curDate, je reçois un message indiquant qu'il ne trouve pas la commande Mon (Source le lundi), par exemple.
thecarpy
@dirtside Ce n'est pas vrai. Même avec l'alias foo = `command` commandn'est exécuté qu'une seule fois. Je l'ai vérifié: function aaa () {printf date; echo aaa >> ~ / test.txt; } alias test1 = aaa. La fonction aaa n'est exécutée qu'une seule fois (après chaque connexion) quel que soit le nombre d' test1exécutions d' alias ( ). J'ai utilisé .bashrc (sur Debian 10).
vitaliydev
Aucune idée, c'était la version de bash que j'utilisais il y a cinq ans et demi, qui était probablement assez ancienne même à ce moment-là. Il est possible que mes tests aient été incorrects, mais cela n'a plus d'importance maintenant car je doute que quiconque utilise encore la version qui était.
dirtside

Réponses:

156

Le principal est la possibilité de les imbriquer , des commandes dans des commandes, sans perdre la raison en essayant de déterminer si une forme d'échappée fonctionnera sur les backticks.

Un exemple, quoique quelque peu artificiel:

deps=$(find /dir -name $(ls -1tr 201112[0-9][0-9]*.txt | tail -1l) -print)

qui vous donnera une liste de tous les fichiers dans l' /dirarborescence de répertoires qui ont le même nom que le premier fichier texte daté de décembre 2011 (a) .

Un autre exemple serait quelque chose comme obtenir le nom (pas le chemin complet) du répertoire parent:

pax> cd /home/pax/xyzzy/plugh
pax> parent=$(basename $(dirname $PWD))
pax> echo $parent
xyzzy

(a) Maintenant que cette commande spécifique peut ne pas fonctionner réellement, je n'ai pas testé la fonctionnalité. Donc, si vous me rejetez pour cela, vous avez perdu de vue l'intention :-) Il s'agit simplement d'une illustration de la façon dont vous pouvez imbriquer, pas comme un extrait de code prêt pour la production sans bogue.

paxdiablo
la source
87
Je m'attends à ce que tout le code sur SO soit des extraits prêts pour la production, conçus selon les normes de fiabilité du code de navette de la NASA. Tout ce qui est moins obtient un drapeau et un vote de suppression.
DVK
1
@DVK Au cas où vous ne plaisanteriez pas, je ne suis pas d'accord avec le fait que les contributions au code devraient être signalées pour ne pas avoir automatiquement reconduit les licences des SO par défaut (licences CC-BY-SA ou MIT) afin de permettre de telles garanties ou l'adéquation à un objectif. Je réutiliserais plutôt le code sur SO à mes propres risques et voterais les contributions en fonction de l'utilité, des mérites techniques, etc.
chrstphrchvz
9
@chrstphrchvz, si vous regardez mon profil, vous verrez ce petit extrait: "Tout le code que je poste sur Stack Overflow est couvert par la licence" Faites ce que vous voulez avec lui ", dont le texte intégral est: Do whatever the heck you want with it." :-) En tout cas, je suis à peu près certain que c'était de l'humour de DVK.
paxdiablo
1
mais qu'en est-il si c'était "cd / home / pax / xyzzy / plover"? Vous retrouveriez-vous dans un labyrinthe de petits passages sinueux, tous différents?
Duncan
@wchargin saviez-vous que cet incident était l'un des principaux facteurs de motivation pour l'ajout de littéraux définis utilisés au langage C ++? en.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod
59

Supposons que vous souhaitiez trouver le répertoire lib correspondant à l'emplacement d' gccinstallation. Tu as le choix:

libdir=$(dirname $(dirname $(which gcc)))/lib

libdir=`dirname \`dirname \\\`which gcc\\\`\``/lib

Le premier est plus facile que le second - utilisez le premier.

Jonathan Leffler
la source
4
Ce serait bien de voir quelques citations autour de ces substitutions de commandes!
Tom Fenech
1
Au moins pour bash, le commentaire de @TomFenech ne s'applique pas aux affectations. x=$(f); x=`f` se comportent de la même manière que x="$(f)"; x="`f`". En revanche, l' attribution de tableau n'effectuer le fractionnement à caractères comme prévu lors de l' appel des commandes. C'est pratique ( n'a pas de sens) mais incohérent. x=($(f)); x=(`f`) $IFSx=1 2 3 4
kdb
@kdb vous avez raison sur le x=$(f)travail sans guillemets. J'aurais dû être plus précis; Je proposais d'utiliser libdir=$(dirname "$(dirname "$(which gcc)")")/lib(des guillemets autour des substitutions de commandes internes ). Si vous ne les laissez pas entre guillemets, vous êtes toujours soumis à la division habituelle des mots et à l'expansion globale.
Tom Fenech
38

Le backticks ( `...`) est la syntaxe héritée requise uniquement par le plus ancien des bourne-shells non compatibles $(...)POSIX et est POSIX et plus préféré pour plusieurs raisons:

  • Les backslashes ( \) à l'intérieur des backticks sont gérés de manière non évidente:

    $ echo "`echo \\a`" "$(echo \\a)"
    a \a
    $ echo "`echo \\\\a`" "$(echo \\\\a)"
    \a \\a
    # Note that this is true for *single quotes* too!
    $ foo=`echo '\\'`; bar=$(echo '\\'); echo "foo is $foo, bar is $bar" 
    foo is \, bar is \\
  • Les citations imbriquées à l'intérieur $()sont beaucoup plus pratiques:

    echo "x is $(sed ... <<<"$y")"

    au lieu de:

    echo "x is `sed ... <<<\"$y\"`"

    ou écrire quelque chose comme:

    IPs_inna_string=`awk "/\`cat /etc/myname\`/"'{print $1}' /etc/hosts`

    car $()utilise un contexte entièrement nouveau pour citer

    ce qui n'est pas portable car les shells Bourne et Korn auraient besoin de ces barres obliques inverses, contrairement à Bash et dash.

  • La syntaxe des substitutions de commandes d'imbrication est plus simple:

    x=$(grep "$(dirname "$path")" file)

    que:

    x=`grep "\`dirname \"$path\"\`" file`

    car $()applique un contexte entièrement nouveau pour les guillemets, de sorte que chaque substitution de commande est protégée et peut être traitée seule sans souci particulier pour les guillemets et les échappements. Lorsque vous utilisez des backticks, cela devient plus laid et plus laid après deux niveaux et plus.

    Quelques exemples supplémentaires:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
  • Il résout un problème de comportement incohérent lors de l'utilisation de backquotes:

    • echo '\$x' les sorties \$x
    • echo `echo '\$x'` les sorties $x
    • echo $(echo '\$x') les sorties \$x
  • La syntaxe des backticks a des restrictions historiques sur le contenu de la commande intégrée et ne peut pas gérer certains scripts valides qui incluent des backquotes, tandis que le nouveau $()formulaire peut traiter tout type de script intégré valide.

    Par exemple, ces scripts intégrés valides ne fonctionnent pas dans la colonne de gauche, mais fonctionnent sur l' IEEE de droite :

    echo `                         echo $(
    cat <<\eof                     cat <<\eof
    a here-doc with `              a here-doc with )
    eof                            eof
    `                              )
    
    
    echo `                         echo $(
    echo abc # a comment with `    echo abc # a comment with )
    `                              )
    
    
    echo `                         echo $(
    echo '`'                       echo ')'
    `                              )

Par conséquent, la syntaxe pour la substitution de commande$ -prefixed devrait être la méthode préférée, car elle est visuellement claire avec une syntaxe propre (améliore la lisibilité humaine et machine), elle est imbriquée et intuitive, son analyse interne est séparée et elle est également plus cohérente (avec toutes les autres extensions qui sont analysées entre guillemets) où les backticks sont la seule exception et le caractère est facilement camouflé lorsqu'il est adjacent, ce qui le rend encore plus difficile à lire, en particulier avec des polices petites ou inhabituelles.`"

Source: Pourquoi est-il $(...)préférable aux `...`(backticks)? chez BashFAQ

Voir également:

Kenorb
la source
1
Les guillemets imbriqués dans la substitution de style accent gravis sont en fait indéfinis, vous pouvez utiliser des guillemets doubles à l' extérieur ou à l' intérieur mais pas les deux, de manière portable. Les coquillages les interprètent différemment; certains nécessitent une barre oblique inverse pour les échapper, d'autres exigent qu'ils ne soient pas échappés par une barre oblique inverse.
mirabilos
23

De man bash:

          $(command)
   or
          `command`

   Bash performs the expansion by executing command and replacing the com-
   mand  substitution  with  the  standard output of the command, with any
   trailing newlines deleted.  Embedded newlines are not deleted, but they
   may  be  removed during word splitting.  The command substitution $(cat
   file) can be replaced by the equivalent but faster $(< file).

   When the old-style backquote form of substitution  is  used,  backslash
   retains  its  literal  meaning except when followed by $, `, or \.  The
   first backquote not preceded by a backslash terminates the command sub-
   stitution.   When using the $(command) form, all characters between the
   parentheses make up the command; none are treated specially.
dgw
la source
9

En plus des autres réponses,

$(...)

se démarque visuellement mieux que

`...`

Les backticks ressemblent trop à des apostrophes; cela varie en fonction de la police que vous utilisez.

(Et, comme je viens de le remarquer, les backticks sont beaucoup plus difficiles à saisir dans les échantillons de code en ligne.)

Keith Thompson
la source
2
vous devez avoir un clavier bizarre (ou moi?). Pour moi, il est beaucoup plus facile de taper des backticks - ils sont la touche du coin supérieur gauche, pas de SHIFT ou ALT requis.
DVK
1
@DVK: Je parlais de leur apparence, pas de leur facilité de frappe. (Mon clavier est probablement le même que le vôtre.) Pourtant, maintenant que vous en parlez, je pense que j'ai une meilleure mémoire musculaire pour $ (et )que pour le backtick; YMMV.
Keith Thompson
jamais programmé en bash (passé de l'ancien ksh à Perl) donc certainement pas de mémoire pour cette syntaxe spécifique :)
DVK
1
@DVK, je pensais que Keith faisait référence au fait que le code non-bloc ici (le code de bloc signifie utiliser quatre espaces au début de la ligne) utilise des backticks pour l'indiquer, ce qui rend difficile leur insertion, une autre illustration du problèmes d'imbrication :-) FWIW, vous pouvez trouver que les balises code et / code (l'autre façon de faire du code non-bloc) peuvent plus facilement contenir des contre-indications.
paxdiablo
@Pax - compris. Duh! J'étais en effet mentalement coincé sur les codes de bloc pour une raison quelconque.
DVK
7

$() permet l'imbrication.

out=$(echo today is $(date))

Je pense que les backticks ne le permettent pas.

Shiplu Mokaddim
la source
2
Vous pouvez imbriquer des backticks; il est beaucoup plus difficile: out=`echo today is \`date\`` .
Jonathan Leffler
4

C'est le standard POSIX qui définit la $(command)forme de substitution de commande. La plupart des shells utilisés aujourd'hui sont compatibles POSIX et prennent en charge cette forme préférée par rapport à la notation archaïque backtick. La section de substitution de commande (2.6.3) du document Shell Language décrit ceci:

La substitution de commande permet de remplacer la sortie d'une commande à la place du nom de commande lui-même. La substitution de commande doit avoir lieu lorsque la commande est incluse comme suit:

$(command)

ou (version rétrospective):

`command`

Le shell étendra 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) par la sortie standard de la commande, en supprimant séquences d'un ou plusieurs <newline>caractères à la fin de la substitution. Les <newline>caractères incorporés avant la fin de la sortie ne doivent pas être supprimés; cependant, ils peuvent être traités comme des délimiteurs de champ et éliminés lors de la division de champ, en fonction de la valeur de IFS et de la cotation en vigueur. Si la sortie contient des octets nuls, le comportement n'est pas spécifié.

Dans le style de substitution de commande entre guillemets, <backslash>conservera sa signification littérale, sauf s'il est suivi de: « $», « `» ou <backslash>. La recherche du backquote correspondant doit être satisfaite par le premier backquote non échappé sans guillemets; pendant cette recherche, si un retour arrière non échappé est rencontré dans un commentaire de shell, un document ici, une substitution de commande intégrée de la forme $ ( commande ) ou une chaîne entre guillemets, des résultats indéfinis se produisent. Une chaîne entre guillemets simples ou doubles qui commence, mais ne se termine pas, dans la `...`séquence " " produit des résultats non définis.

Avec le formulaire $ ( command ), tous les caractères suivant la parenthèse ouverte jusqu'à la parenthèse fermante correspondante constituent la commande . Tout script shell valide peut être utilisé pour la commande , à l'exception d'un script composé uniquement de redirections qui produit des résultats non spécifiés.

Les résultats de la substitution de commande ne doivent pas être traités pour une extension supplémentaire du tilde, une extension de paramètre, une substitution de commande ou une extension arithmétique. Si une substitution de commande se produit entre guillemets doubles, la division de champ et le développement du chemin ne doivent pas être effectués sur les résultats de la substitution.

La substitution de commande peut être imbriquée. Pour spécifier l'imbrication dans la version avec guillemets inversés, l'application doit précéder les guillemets intérieurs avec des <backslash>caractères; par exemple:

\`command\`

La syntaxe du langage de commande shell présente une ambiguïté pour les extensions commençant par "$((", qui peut introduire un développement arithmétique ou une substitution de commande qui commence par un sous-shell. Le développement arithmétique a la priorité; autrement dit, le shell doit d'abord déterminer s'il peut analyser le développement comme un développement arithmétique et ne doit analyser le développement que comme une commande substitution s'il détermine qu'il ne peut pas analyser le développement en tant que développement arithmétique. Le shell n'a pas besoin d'évaluer les développements imbriqués lors de cette détermination. S'il rencontre la fin de l'entrée sans avoir déjà déterminé qu'il ne peut pas analyser le développement comme un développement arithmétique, le shell doit traiter l'expansion comme une expansion arithmétique incomplète et signaler une erreur de syntaxe. Une application conforme doit s'assurer qu'elle sépare le " $(" et le '('en deux jetons (c'est-à-dire, séparez-les par un espace blanc) dans une substitution de commande qui commence par un sous-shell. Par exemple, une substitution de commande contenant un seul sous-shell pourrait être écrite comme suit:

$( (command) )

JRFerguson
la source
4

C'est une question héritée, mais je suis venu avec un exemple parfaitement valable de $(...)over `...`.

J'utilisais un bureau à distance pour Windows exécutant cygwin et je voulais itérer sur le résultat d'une commande. Malheureusement, le caractère backtick était impossible à saisir, soit à cause du bureau à distance, soit de cygwin lui-même.

Il est raisonnable de supposer qu'un signe dollar et des parenthèses seront plus faciles à saisir dans des configurations aussi étranges.

Piotr Zierhoffer
la source