Con las variables Bash, ¿cuál es la diferencia entre $myvar y "$myvar"? (Comportamiento extraño específico)

Con las variables Bash, ¿cuál es la diferencia entre $myvar y "$myvar"? (Comportamiento extraño específico)

Pregunta general:

En Bash, sé que el uso de la variable myvarse puede hacer de dos maneras:

# Define a variable:
bash$ myvar="two words"

# Method one to dereference:
bash$ echo $myvar
two words

# Method two to dereference:
bash$ echo "$myvar"
two words

En el caso anterior, el comportamiento es idéntico. Esto se debe a cómo echofunciona. En otras utilidades de Unix, si las palabras se agrupan entre comillas dobles hará una gran diferencia:

bash$ myfile="Cool Song.mp3"
bash$ rm "$myfile"            # Deletes "Cool Song.mp3".
bash$ rm $myfile              # Tries to delete "Cool" and "Song.mp3".

Me pregunto cuál es el significado más profundo de esta diferencia. Lo más importante es ¿cómo puedo ver exactamente lo que se pasará al comando, para poder ver si está entrecomillado correctamente?

Ejemplo extraño específico:

Simplemente escribiré el código con el comportamiento observado:

bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate"    # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.

¿Por qué necesito las comillas dobles? ¿Qué ve exactamente git-log después de que se elimina la referencia a la variable sin comillas dobles?

Pero ahora mira esto:

bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace        # Now THIS works great.
bash$ git log "$nospace"      # This kind of works, here is a snippet:

# From git-log output:
Date:   "2018-04-12"

Qué asco, ¿por qué la salida impresa ahora tiene comillas dobles? Parece que si las comillas dobles son innecesarias, no se eliminan, sino que se interpretan como caracteres de comillas literales si y sólo si no fueran necesarias.

¿Qué se pasa Git como argumentos?Ojalá supiera cómo averiguarlo.

Para hacer las cosas más complejas, escribí un script en Python argparseque simplemente imprime todos los argumentos (tal como los interpretó Bash, es decir, con literales entre comillas dobles donde Bash cree que son parte del argumento, y con palabras agrupadas o no agrupadas como Bash lo considere oportuno), y el argparsescript Python se comporta de manera muy racional. Lamentablemente, creo que argparsees posible que esté solucionando silenciosamente un problema conocido con Bash y, por lo tanto, ocultando las cosas desordenadas que Bash le está pasando. Es sólo una suposición, no tengo idea. Tal vez git-log esté arruinando en secreto lo que Bash le está pasando.

O tal vez simplemente no sé qué está pasando en absoluto.

Gracias.

Editado Editar:Permítanme decir esto ahora, antes de que haya respuestas: sé que puedotal vezuse comillas simples alrededor de todo y luego no escape de las comillas dobles. En realidad, esto funciona algo mejor para mi problema inicial al usar git-log, pero lo probé en otros contextos y es igualmente impredecible y poco confiable. Algo extraño está sucediendo al citar variables internas. Ni siquiera voy a publicar todas las cosas raras que sucedieron entre comillas simples.

Edición 2: esto tampoco funciona:Se me acaba de ocurrir esta maravillosa idea, pero no funciona en absoluto:

bash$ mydate="--date=format:%Y-%m-%d\ T%H"
bash$ git log "$mydate"

# Git log output has this:
Date:   2018-04-12\ T23

Entonces no tiene comillas envolviéndolo,perotiene un carácter de barra invertida literal en la cadena de fecha. Además, git log $mydatesin errores de comillas, con el espacio de barra invertida en la variable.

Respuesta1

Enfoque diferente:

Cuando ejecutas git log --format="foo bar", git no interpreta esas comillas; el shell las elimina (y protegen el texto citado para que no se divida). Esto da como resultado un solo argumento:

  • --format=foo bar

Sin embargo, cuando no está citadovariablesse amplían, los resultados pasan por la división de palabras, peronoa través de comillas. Entonces, si su variable contiene --format="foo bar", se expande a estos argumentos:

  • --format="foo
  • bar"

Esto se puede verificar usando:

  • printf '%s\n' $variable

...así como cualquier script simple que imprima los argumentos recibidos.

  • #!/usr/bin/env perl
    por $i (0..$#ARGV) {
        imprimir ($i+1)." = ".$ARGV[$i]."\n";
    }
    
  • #!/usr/bin/env python3
    sistema de importación
    para i, arg en enumerar (sys.argv):
        imprimir(yo, "=", argumento)
    

Si siempre tiene bash disponible, la solución preferida es usarformaciónvariables:

myvar=( --format="foo bar" )

Con esto, el análisis habitual se realiza durante la asignación, no durante la expansión. Utilice esta sintaxis para expandir el contenido de la variable, cada elemento obtiene su propio argumento:

git log "${myvar[@]}"

Respuesta2

¿Por qué su comando original no funciona?

bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate"    # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.

Usted pregunta:

¿Por qué necesito las comillas dobles? ¿Qué ve exactamente git-log después de que se elimina la referencia a la variable sin comillas dobles?

Si no utiliza comillas dobles alrededor de $mydate, la variable se expandirá palabra por palabra y la línea del shell será la siguiente antes de ejecutarse:

git log --date=format:"%Y-%m-%d T%H"
                      ^————————————^—————— literal quotes

Aquí, agregaste (innecesariamente) comillas literales usando \"en la asignación de variables.

Dado que el comando se someterádivisión de palabras, gitrecibirá tres argumentos y log, por lo tanto, se quejará de no encontrar ningún compromiso u objeto llamado .--date-format:"%Y-%m%-dT%H"T%H"


¿Cuál es el enfoque correcto?

Si desea mantener los argumentos juntos, si ese argumento contiene espacios en blanco, debe encerrarlo entre comillas.Generalmente, siempre incluya las variables entre comillas dobles.

Esto funciona incluso si hay un espacio dentro de la variable:

mydate="--date=format:%Y-%m-%d T%H"
git log "$mydate"

Ahora el tercer argumento gitserá $mydate, incluido el espacio que especificó originalmente. El shell elimina todas las comillas antes de pasarlas a git.

Simplemente no necesita comillas adicionales: si lo único que desea es gitver un argumento, envuélvalo entre comillas al pasar la variable "$mydate".


Además preguntas:

bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace        # Now THIS works great.
bash$ git log "$nospace"      # This kind of works, here is a snippet:

# From git-log output:
Date:   "2018-04-12"

Tu pregunta:

Qué asco, ¿por qué la salida impresa ahora tiene comillas dobles?

Porque has vuelto a incluirliteralcomillas en el argumento (escapándolas), que se convierten, por ejemplo, en comillas "reales" cuando olvidas citar la variable en tu comando real. Digo "olvidar" porque el uso de variables sin comillas en los comandos del shell normalmente solo te mete en problemas, y aquí se trata de revertir un error que cometiste al especificar la variable en primer lugar.

PD: Sé que todo esto es confuso, pero así es Bash y sigue algunas reglas claras. No hay ningún error aquí. Aensayo relacionadosobre los nombres de archivos en Shell también es muy revelador, ya que toca el tema del manejo de espacios en blanco en Bash.

información relacionada