¿Cuál es el beneficio de usar $ () en lugar de backticks en scripts de shell?

175

Hay dos formas de capturar la salida de la línea de comando en bash:

  1. Backticks de shell Bourne heredados ``:

    var=`command`
  2. $() sintaxis (que hasta donde yo sé es específica de Bash, o al menos no es compatible con shells antiguos que no son POSIX como Bourne original)

    var=$(command)

¿Hay algún beneficio al usar la segunda sintaxis en comparación con los backticks? ¿O son los dos totalmente 100% equivalentes?

DVK
fuente
15
$()es POSIX y es compatible con todos los shells Bourne modernos, por ejemplo, ksh, bash, ash, dash, zsh, busybox, lo que sea. (Uno no tan moderno es Solaris /bin/sh, pero en Solaris se aseguraría de usar el moderno en su /usr/xpg4/bin/shlugar).
Jens
27
Además, una nota sobre el uso $()y los backticks en los alias. Si tiene alias foo=$(command)en su, .bashrcentonces commandse ejecutará cuando se ejecute el comando alias durante la .bashrcinterpretación. Con alias foo=`command`, commandse ejecutará cada vez que se ejecute el alias. Pero si escapa $con el $()formulario (por ejemplo alias foo=\$(command)), también se ejecutará cada vez que se ejecute el alias, en lugar de durante la .bashrcinterpretación. Por lo que puedo decir por prueba, de todos modos; No puedo encontrar nada en los documentos de bash que explique este comportamiento.
dirtside
44
@dirtside ¿Qué shell es este? He probado bash y POSIX shell, el backtick se ejecuta cuando lo hago. Ejemplo simple: alias curDate = `date` Después de que lo obtengo y ejecuto curDate, recibo un mensaje de que no puede encontrar el comando Mon (Sourced el lunes), por ejemplo.
thecarpy
@dirtside No es cierto. Incluso con alias foo = `command` commandse ejecuta solo una vez. Lo comprobé: function aaa () {printf date; echo aaa >> ~ / test.txt; } alias test1 = aaa. La función aaa se ejecuta solo una vez (después de cada inicio de sesión) sin importar cuántas veces test1se haya ejecutado alias ( ). Usé .bashrc (en Debian 10).
vitaliydev
No tengo idea, era la versión de bash que estaba usando hace cinco años y medio, lo que probablemente era bastante viejo incluso en ese momento. Es posible que mis pruebas hayan sido incorrectas, pero ahora apenas importa porque dudo que alguien siga usando la versión que sea.
tierra

Respuestas:

156

La principal es la capacidad de anidarlos , comandos dentro de los comandos, sin perder la cordura tratando de descubrir si alguna forma de escapar funcionará en los backticks.

Un ejemplo, aunque algo artificial:

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

que le dará una lista de todos los archivos en el /dirárbol de directorios que tienen el mismo nombre que el archivo de texto con fecha más temprana de diciembre de 2011 (a) .

Otro ejemplo sería algo así como obtener el nombre (no la ruta completa) del directorio principal:

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

(a) Ahora que el comando específico puede no funcionar realmente, no he probado la funcionalidad. Por lo tanto, si me rechaza, ha perdido de vista la intención :-) Se entiende solo como una ilustración de cómo puede anidar, no como un fragmento listo para la producción libre de errores.

paxdiablo
fuente
87
Espero que todo el código en SO sea fragmentos listos para producción, diseñados según los estándares de confiabilidad del código de lanzadera de la NASA. Cualquier cosa menos obtiene una bandera y un voto de eliminación.
DVK
1
@DVK En caso de que no esté bromeando, no estoy de acuerdo con que las contribuciones de código se marquen por no autorizar automáticamente la renuncia a los valores predeterminados de SO (licencias CC-BY-SA o MIT) para permitir tales garantías o adecuación de propósito. En vez de ello la reutilización de código en el SO en mi propio riesgo, y las contribuciones voto de acuerdo con amabilidad, méritos técnicos, etc.
chrstphrchvz
9
@chrstphrchvz, si miras mi perfil, verás este pequeño fragmento: "Todo el código que publico en Stack Overflow está cubierto por la licencia" Haz lo que quieras con él ", cuyo texto completo es: Do whatever the heck you want with it." :-) En cualquier caso, estoy bastante seguro de que fue humor de DVK.
paxdiablo
1
pero ¿qué pasa si era "cd / home / pax / xyzzy / plover"? ¿Te encontrarás en un laberinto de pequeños pasajes sinuosos, todos diferentes?
Duncan
@wchargin, ¿sabía usted que este incidente fue un motivador principal para la adición de literales definidos usados ​​al lenguaje C ++? en.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod
59

Suponga que desea encontrar el directorio lib correspondiente al lugar donde gccestá instalado. Tienes una opción:

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

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

El primero es más fácil que el segundo: use el primero.

Jonathan Leffler
fuente
44
¡Sería bueno ver algunas citas sobre esas sustituciones de comandos!
Tom Fenech
1
Al menos para bash, el comentario de @TomFenech no se aplica a las tareas. x=$(f); x=`f` comportarse igual que x="$(f)"; x="`f`". Por el contrario, la asignación de matrices x=($(f)); x=(`f`) hacer realizar a dividir en $IFScaracteres como se esperaba cuando se invoca comandos. Esto es conveniente ( x=1 2 3 4no tiene sentido) pero inconsistente.
kdb
@kdb tienes razón sobre el x=$(f)trabajo sin comillas. Debería haber sido más específico; Estaba proponiendo usar libdir=$(dirname "$(dirname "$(which gcc)")")/lib(citas alrededor de las sustituciones de comandos internos ). Si no se cita, aún está sujeto a la división habitual de palabras y la expansión global.
Tom Fenech
38

El backticks ( `...`) es la sintaxis heredada requerida solo por las capas de bourne más antiguas no compatibles con POSIX y $(...)es POSIX y más preferido por varias razones:

  • Las barras invertidas ( \) dentro de los backticks se manejan de una manera no obvia:

    $ 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 \\
  • Las citas anidadas en el interior $()son mucho más convenientes:

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

    en vez de:

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

    o escribiendo algo como:

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

    porque $()usa un contexto completamente nuevo para citar

    que no es portátil ya que los shells Bourne y Korn requerirían estas barras invertidas, mientras que Bash y dash no.

  • La sintaxis para las sustituciones de comandos de anidación es más fácil:

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

    que:

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

    porque $()impone un contexto completamente nuevo para las citas, por lo que cada sustitución de comandos está protegida y puede tratarse por sí sola sin especial preocupación por las citas y las escapadas. Al usar backticks, se vuelve más y más feo después de dos niveles o más.

    Pocos ejemplos más:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
  • Resuelve un problema de comportamiento inconsistente cuando se utilizan comillas inversas:

    • echo '\$x' salidas \$x
    • echo `echo '\$x'` salidas $x
    • echo $(echo '\$x') salidas \$x
  • La sintaxis de Backticks tiene restricciones históricas sobre el contenido del comando incrustado y no puede manejar algunos scripts válidos que incluyen comillas inversas, mientras que el $()formulario más nuevo puede procesar cualquier tipo de script incrustado válido.

    Por ejemplo, estos scripts incrustados válidos no funcionan en la columna izquierda, pero sí funcionan en el IEEE derecho :

    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 ')'
    `                              )

Por lo tanto, la sintaxis para la $sustitución de comandos prefijada debería ser el método preferido, ya que es visualmente clara con una sintaxis limpia (mejora la legibilidad humana y mecánica), es anidable e intuitiva, su análisis interno es independiente y también es más consistente (con todas las otras expansiones que se analizan entre comillas dobles) donde los backticks son la única excepción y el `carácter se camufla fácilmente cuando está adyacente para "hacerlo aún más difícil de leer, especialmente con fuentes pequeñas o inusuales.

Fuente: ¿Por qué se $(...)prefiere sobre `...`(backticks)? en BashFAQ

Ver también:

kenorb
fuente
1
Las citas anidadas en la sustitución de acento estilo gravis en realidad no están definidas, puede usar comillas dobles, ya sea fuera o dentro, pero no ambas, de forma portátil. Los proyectiles los interpretan de manera diferente; algunos requieren una barra invertida para escapar de ellos, algunos requieren que no se escape con barra invertida.
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
fuente
9

Además de las otras respuestas,

$(...)

destaca visualmente mejor que

`...`

Los backticks se parecen demasiado a los apóstrofes; esto varía según la fuente que esté utilizando.

(Y, como acabo de notar, los backticks son mucho más difíciles de ingresar en ejemplos de código en línea).

Keith Thompson
fuente
2
debes tener un teclado extraño (¿o yo?). Para mí, es mucho más fácil escribir backticks: son la tecla de la esquina superior izquierda, no se requieren MAYÚS ni ALT.
DVK
1
@DVK: Estaba hablando de su apariencia, no de la facilidad de escribir. (Mi teclado es probablemente el mismo que el tuyo). Aun así, ahora que lo mencionas, creo que tengo mejor memoria muscular para $ (y )para el backtick; YMMV.
Keith Thompson,
nunca programado en bash (saltado del viejo ksh a Perl), así que definitivamente no hay memoria para esa sintaxis específica :)
DVK
1
@DVK, pensé que Keith se refería al hecho de que el código que no es de bloque aquí (código de bloque significa usar cuatro espacios al comienzo de la línea) usa backticks para indicarlo, lo que dificulta poner backticks en ellos, otra ilustración del dificultades de anidamiento :-) FWIW, puede encontrar que el código y las etiquetas de código (la otra forma de hacer un código sin bloque) pueden contener más fácilmente backticks.
paxdiablo
@Pax: lo tengo. Duh! De hecho, estaba mentalmente atrapado en los códigos de bloque por alguna razón.
DVK
7

$() permite anidar.

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

Creo que los backticks no lo permiten.

Shiplu Mokaddim
fuente
2
Puedes anidar backticks; es mucho más difícil: out=`echo today is \`date\`` .
Jonathan Leffler
4

Es el estándar POSIX que define la $(command)forma de sustitución de comandos. La mayoría de los shells en uso hoy en día cumplen con POSIX y admiten esta forma preferida sobre la notación arcaica de backtick. La sección de sustitución de comandos (2.6.3) del documento Shell Language describe esto:

La sustitución de comandos permite sustituir la salida de un comando en lugar del nombre del comando en sí. La sustitución del comando ocurrirá cuando el comando esté encerrado de la siguiente manera:

$(command)

o (versión con comillas):

`command`

El shell expandirá la sustitución del comando ejecutando el comando en un entorno de subshell (consulte Entorno de ejecución de Shell ) y reemplazando la sustitución del comando (el texto del comando más el "$ ()" o comillas anexas) con la salida estándar del comando, eliminando secuencias de uno o más <newline>caracteres al final de la sustitución. Los <newline>caracteres incrustados antes del final de la salida no se eliminarán; sin embargo, pueden tratarse como delimitadores de campo y eliminarse durante la división de campo, según el valor de IFS y las cotizaciones vigentes. Si la salida contiene bytes nulos, el comportamiento no se especifica.

Dentro del estilo de sustitución de comando entre comillas, <backslash>retendrá su significado literal, excepto cuando sea seguido por: ' $', ' `' o <backslash>. La búsqueda de la cita inversa coincidente deberá ser satisfecha por la primera cita inversa no escapada sin comillas; durante esta búsqueda, si se encuentra una comilla inversa no escapada dentro de un comentario de shell, un documento aquí, una sustitución de comando incrustada del formulario $ ( comando ) o una cadena entre comillas, se producen resultados indefinidos. Una cadena entre comillas simples o dobles que comienza, pero no termina, dentro de "`...` secuencia " produce resultados indefinidos.

Con el formulario $ ( comando ), todos los caracteres que siguen el paréntesis abierto al paréntesis de cierre coincidente constituyen el comando . Se puede usar cualquier script de shell válido para el comando , excepto un script que consista únicamente en redirecciones que produzcan resultados no especificados.

Los resultados de la sustitución de comandos no se procesarán para una mayor expansión de tilde, expansión de parámetros, sustitución de comandos o expansión aritmética. Si se produce una sustitución de comando entre comillas dobles, la división del campo y la expansión del nombre de ruta no se realizarán en los resultados de la sustitución.

La sustitución de comandos puede estar anidada. Para especificar el anidamiento dentro de la versión con comillas invertidas, la aplicación precederá las comillas internas con <backslash>caracteres; por ejemplo:

\`command\`

La sintaxis del lenguaje de comandos de shell tiene una ambigüedad para las expansiones que comienzan con "$((", que puede introducir una expansión aritmética o una sustitución de comando que comienza con un subshell. La expansión aritmética tiene prioridad; es decir, el shell primero determinará si puede analizar la expansión como una expansión aritmética y solo analizará la expansión como un comando sustitución si determina que no puede analizar la expansión como una expansión aritmética. El shell no necesita evaluar expansiones anidadas al realizar esta determinación. Si encuentra el final de la entrada sin haber determinado ya que no puede analizar la expansión como una expansión aritmética, el Shell tratará la expansión como una expansión aritmética incompleta e informará un error de sintaxis. Una aplicación conforme se asegurará de que separa el " $(" y '('en dos tokens (es decir, sepárelos con un espacio en blanco) en una sustitución de comando que comienza con una subshell. Por ejemplo, una sustitución de comando que contiene una única subshell podría escribirse como:

$( (command) )

JRFerguson
fuente
4

Esta es una pregunta heredada, pero se me ocurrió un ejemplo perfectamente válido de $(...)más `...`.

Estaba usando un escritorio remoto para Windows ejecutando cygwin y quería iterar sobre el resultado de un comando. Lamentablemente, el carácter de backtick fue imposible de ingresar, ya sea por el escritorio remoto o por el propio cygwin.

Es sensato suponer que un signo de dólar y paréntesis serán más fáciles de escribir en configuraciones tan extrañas.

Piotr Zierhoffer
fuente