¿Cuándo es necesaria la doble cita?

120

El viejo consejo solía ser citar dos veces cualquier expresión que involucrara un $VARIABLE, al menos si uno quisiera que el intérprete lo interpretara como un solo elemento, de lo contrario, cualquier espacio en el contenido de $VARIABLEarrojaría el intérprete.

Sin embargo, entiendo que en versiones más recientes de shells, las comillas dobles ya no siempre son necesarias (al menos para el propósito descrito anteriormente). Por ejemplo, en bash:

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

En zsh, por otro lado, los mismos tres comandos tienen éxito. Por lo tanto, en base a este experimento, parece que, en bash, uno puede omitir las comillas dobles en el interior [[ ... ]], pero no en el interior [ ... ]ni en los argumentos de la línea de comandos, mientras que, en zsh, estos pueden omitirse en todos estos casos.

Pero inferir reglas generales a partir de ejemplos anecdóticos como el anterior es una propuesta arriesgada. Sería bueno ver un resumen de cuándo es necesaria una doble cita. Estoy interesado principalmente en zsh, bashy /bin/sh.

kjo
fuente
10
Su comportamiento observado en zsh depende de la configuración y está influenciado por la SH_WORD_SPLITopción.
Ulrich Dangel
3
Como comentario aparte, los nombres de variables en mayúsculas son utilizados por variables con significado para el sistema operativo y el shell; La especificación POSIX recomienda explícitamente el uso de nombres en minúsculas para las variables definidas por la aplicación. (Si bien la especificación citada se centra específicamente en las variables de entorno, las variables de entorno y las variables de shell comparten un espacio de nombres: al intentar crear una variable de shell con un nombre ya utilizado por una variable de entorno, se sobrescribe esta última). Ver pubs.opengroup.org/onlinepubs/009695399/basedefs/… , cuarto párrafo.
Charles Duffy

Respuestas:

128

Primero, separe zsh del resto. No se trata de conchas antiguas versus modernas: zsh se comporta de manera diferente. Los diseñadores de zsh decidieron hacerlo incompatible con los shells tradicionales (Bourne, ksh, bash), pero más fáciles de usar.

En segundo lugar, es mucho más fácil usar comillas dobles todo el tiempo que recordar cuándo son necesarias. Se necesitan la mayor parte del tiempo, por lo que deberá aprender cuándo no se necesitan, no cuando se necesitan.

En pocas palabras, las comillas dobles son necesarias donde se espera una lista de palabras o un patrón . Son opcionales en contextos donde el analizador espera una cadena sin formato.

Que pasa sin comillas

Tenga en cuenta que sin comillas dobles, suceden dos cosas.

  1. Primero, el resultado de la expansión (el valor de la variable para una sustitución de parámetro como ${foo}, o la salida del comando para una sustitución de comando como $(foo)) se divide en palabras donde contiene espacios en blanco.
    Más precisamente, el resultado de la expansión se divide en cada carácter que aparece en el valor de la IFSvariable (carácter separador). Si una secuencia de caracteres separadores contiene espacios en blanco (espacio, tabulación o nueva línea), el espacio en blanco se cuenta como un solo carácter; Los separadores iniciales, finales o repetidos que no son espacios en blanco conducen a campos vacíos. Por ejemplo, con IFS=" :", :one::two : three: :four produce campos vacíos antes one, entre oney two, y (uno solo) entre threey four.
  2. Cada campo que resulta de la división se interpreta como un globo (un patrón comodín) si contiene uno de los caracteres \[*?. Si ese patrón coincide con uno o más nombres de archivo, el patrón se reemplaza por la lista de nombres de archivo coincidentes.

Una expansión de variable sin comillas $foose conoce coloquialmente como el "operador split + glob", en contraste con el "$foo"cual solo toma el valor de la variable foo. Lo mismo ocurre con la sustitución de comandos: "$(foo)"es una sustitución de comandos, $(foo)es una sustitución de comandos seguida de split + glob.

Donde puedes omitir las comillas dobles

Estos son todos los casos en los que puedo pensar en un shell de estilo Bourne donde puede escribir una variable o una sustitución de comando sin comillas dobles, y el valor se interpreta literalmente.

  • En el lado derecho de una tarea.

    var=$stuff
    a_single_star=*
    

    Tenga en cuenta que necesita las comillas dobles después export, porque es una construcción ordinaria, no una palabra clave. Esto solo es cierto en algunos shells como dash, zsh (en emulación sh), yash o posh; bash y ksh ambos tratan exportespecialmente.

    export VAR="$stuff"
  • En un casecomunicado.

    case $var in 

    Tenga en cuenta que necesita comillas dobles en un patrón de caso. La división de palabras no ocurre en un patrón de caso, pero una variable sin comillas se interpreta como un patrón, mientras que una variable entre comillas se interpreta como una cadena literal.

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
    
  • Entre corchetes dobles. Los corchetes dobles son una sintaxis especial de shell.

    [[ -e $filename ]]

    Excepto que necesita comillas dobles donde se espera un patrón o una expresión regular: en el lado derecho de =or ==o !=or =~.

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi
    

    Necesita comillas dobles como de costumbre entre corchetes simples [ … ]porque son una sintaxis de shell ordinaria (es un comando que se llama [). Ver paréntesis simples o dobles

  • En una redirección en shells POSIX no interactivos (no bash, ni ksh88).

    echo "hello world" >$filename

    Algunos shells, cuando son interactivos, tratan el valor de la variable como un patrón comodín. POSIX prohíbe ese comportamiento en shells no interactivos, pero algunos shells como bash (excepto en el modo POSIX) y ksh88 (incluso cuando se encuentran como (supuestamente) POSIX shde algunos Unices comerciales como Solaris) todavía lo hacen allí ( bashtambién intenta dividir y la redirección falla a menos que divididos + englobamiento resultados en exactamente una palabra), por lo que es mejor para citar a los objetivos de redirecciones en una shsecuencia de comandos en caso de que quiera convertirlo en un bashguión algún día, o ejecutarlo en un sistema en el que shestá no cumple con ese punto, o puede provenir de shells interactivos.

  • Dentro de una expresión aritmética. De hecho, debe dejar las comillas para que una variable se analice como una expresión aritmética.

    expr=2*2
    echo "$(($expr))"
    

    Sin embargo, necesita las comillas alrededor de la expansión aritmética, ya que están sujetas a la división de palabras en la mayoría de los shells según lo requiera POSIX (!?).

  • En un subíndice de matriz asociativa.

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"
    

Una sustitución de comando y variable sin comillas puede ser útil en algunas circunstancias poco frecuentes:

  • Cuando el valor variable o la salida del comando consta de una lista de patrones globales y desea expandir estos patrones a la lista de archivos coincidentes.
  • Cuando sabe que el valor no contiene ningún carácter comodín, eso $IFSno se modificó y desea dividirlo en caracteres de espacio en blanco.
  • Cuando desee dividir un valor en un determinado carácter: desactive el globbing con set -f, establezca IFSel carácter separador (o déjelo solo para dividir en espacios en blanco), luego realice la expansión.

Zsh

En zsh, puede omitir las comillas dobles la mayoría de las veces, con algunas excepciones.

  • $varnunca se expande a varias palabras, sin embargo, se expande a la lista vacía (a diferencia de una lista que contiene una sola palabra vacía) si el valor de vares la cadena vacía. Contraste:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo
    

    Del mismo modo, se "${array[@]}"expande a todos los elementos de la matriz, mientras que $arraysolo se expande a los elementos no vacíos.

  • La @bandera de la expansión de parámetros a veces requiere comillas dobles alrededor de toda la substitución: "${(@)foo}".

  • La sustitución de comandos sufre división de campo si no está entre comillas: echo $(echo 'a'; echo '*')imprime a *(con un solo espacio) mientras que echo "$(echo 'a'; echo '*')"imprime la cadena de dos líneas no modificada. Utilícelo "$(somecommand)"para obtener la salida del comando en una sola palabra, sin líneas nuevas finales. Utilícelo "${$(somecommand; echo _)%?}"para obtener el resultado exacto del comando, incluidas las nuevas líneas finales. Use "${(@f)$(somecommand)}"para obtener una matriz de líneas de la salida del comando.

Gilles
fuente
De hecho, debe dejar las comillas para que una variable se analice como una expresión aritmética. ¿Por qué puedo hacer que su ejemplo funcione con citas?echo "$(("$expr"))"
Cyker
Esto es lo que man bashdice: la expresión se trata como si estuviera entre comillas dobles, pero una comilla doble entre paréntesis no se trata especialmente.
Cyker
44
Además, para cualquiera que esté interesado, los nombres formales de split + glob son división de palabras y expansión de nombre de ruta .
Cyker
3
FYI: en StackOverflow , alguien ha extraído el lenguaje "opcional cuando se espera una cadena sin procesar" en esta respuesta para defender el hecho de no citar un argumento echo. Puede valer la pena intentar que el lenguaje sea aún más explícito (¿"cuando el analizador espera una cadena sin procesar", quizás?)
Charles Duffy
2
@CharlesDuffy Ugh, no había pensado en esta lectura errónea. Cambié "dónde" por "cuándo" y reforcé la oración como usted sugirió.
Gilles