Com variáveis Bash, qual é a diferença entre $ myvar e “$ myvar”? (Comportamento estranho específico)

2

Pergunta geral:

No Bash, sei que usar a variável myvar pode ser feito de duas maneiras:

# 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

No caso acima, o comportamento é idêntico. Isso é por causa de como echo funciona. Em outros utilitários Unix, se as palavras são agrupadas com aspas duplas, fará uma grande diferença:

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

Eu estou querendo saber qual é o significado mais profundo dessa diferença. Mais importante ainda, como posso ver exatamente o que será passado para o comando, para que eu possa ver se ele é citado corretamente?

Exemplo estranho específico:

Escreverei apenas o código com o comportamento 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 que preciso das aspas duplas? O que exatamente o git-log está vendo depois que a variável é desreferenciada sem aspas duplas?

Mas agora veja isto:

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"

Por que a saída impressa tem aspas duplas agora? Parece que se as aspas duplas forem desnecessárias, elas não serão removidas, elas serão interpretadas como caracteres de aspas literais se e somente se não forem necessárias.

O que Git está sendo passado como argumentos? Eu gostaria de saber como descobrir.

Para tornar as coisas mais complexas, eu escrevi um script Python usando argparse que apenas imprime todos os argumentos (como Bash os interpretou, assim com literais de aspas duplas onde Bash pensa que eles são parte do argumento, e com palavras agrupados ou não agrupados como Bash considera adequado) e o script argparse do Python se comporta de maneira muito racional. Infelizmente, acho que argparse pode estar silenciosamente corrigindo um problema conhecido com o Bash e, assim, obscurecendo as coisas bagunçadas que o Bash está transmitindo para ele. Isso é só um palpite, não faço ideia. Talvez o git-log esteja secretamente estragando o que o Bash está transmitindo para ele.

Ou talvez eu simplesmente não saiba o que está acontecendo.

Obrigado.

Edição editada: Deixe-me dizer isso agora, antes que haja alguma resposta: Eu sei que posso talvez usar aspas simples em torno da coisa toda e depois não escapar do aspas duplas. Isso realmente funciona um pouco melhor para o meu problema inicial usando git-log, mas eu testei em alguns outros contextos e é quase igualmente imprevisível e não confiável. Algo estranho está acontecendo com citações dentro de variáveis. Eu nem vou postar todas as coisas estranhas que aconteceram com aspas simples.

Editar 2 - Isso também não funciona: Acabei de ter essa ideia maravilhosa, mas não funciona:

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

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

Para que não haja cotações, mas ele tem um caractere de barra invertida literal na string de data. Além disso, git log $mydate sem erros de aspas, com o espaço de barra invertida na variável.

    
por SerMetAla 13.04.2018 / 08:34

2 respostas

4

Diferente abordagem:

Quando você executa git log --format="foo bar" , essas aspas não são interpretadas pelo git - elas são removidas pelo shell (e protegem o texto citado da divisão). Isso resulta em um único argumento:

  • --format=foo bar

No entanto, quando as variáveis não expandidas são expandidas, os resultados passam pela divisão de palavras, mas não por meio da não marcação. Portanto, se sua variável contiver --format="foo bar" , ela será expandida para esses argumentos:

  • --format="foo
  • bar"

Isso pode ser verificado usando:

  • printf '%s\n' $variable

... bem como qualquer script simples que imprima seus argumentos recebidos.

  • #!/usr/bin/env perl
    for $i (0..$#ARGV) {
        print ($i+1)." = ".$ARGV[$i]."\n";
    }
    
  • #!/usr/bin/env python3
    import sys
    for i, arg in enumerate(sys.argv):
        print(i, "=", arg)
    

Se você sempre tiver o bash disponível, a solução preferida é usar as variáveis array :

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

Com isso, a análise usual é feita durante a atribuição, não durante a expansão. Você usa essa sintaxe para expandir o conteúdo da variável, cada elemento obtendo seu próprio arg:

git log "${myvar[@]}"
    
por 13.04.2018 / 09:21
2

Por que seu comando original não 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.

Você pergunta:

Why do I need the double-quotes? What exactly is git-log seeing after the variable is dereferenced without double-quotes?

Se você não usar aspas duplas em torno de $mydate , a variável será expandida textualmente e a linha do shell será a seguinte antes de ser executada:

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

Aqui, você (desnecessariamente) adicionou citações literais usando \" na atribuição da variável.

Como o comando será submetido a divisão de palavras , git receberá três argumentos, log , --date-format:"%Y-%m%-d e T%H" , reclamando, portanto, sobre não encontrar nenhum commit ou objeto chamado T%H" .

Qual é a abordagem correta?

Se você quiser manter os argumentos juntos, se esse argumento contiver espaço em branco, será necessário incluir o argumento entre aspas. Geralmente, sempre envolva variáveis entre aspas duplas.

Isso funciona mesmo se houver um espaço dentro da variável:

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

Agora, o terceiro argumento para git será $mydate , incluindo o espaço que você especificou originalmente. Todas as aspas são removidas pelo shell antes de serem passadas para git .

Você simplesmente não precisa da citação adicional - se tudo que você quer é git para ver um argumento, coloque esse argumento entre aspas ao passar a variável "$mydate" .

Além disso, você pergunta:

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"

Sua pergunta:

Yuck, why does the printed output have double-quotes in it now?

Porque você incluiu novamente literal aspas no argumento (escapando delas), que são transformadas em, digamos, aspas “reais” quando você esquece de citar a variável em seu comando real. Eu digo “esqueça” porque usar variáveis não-citadas em comandos de shell geralmente está causando problemas - e aqui está invertendo um erro que você fez ao especificar a variável em primeiro lugar.

PS: Eu sei que isso é confuso, mas isso é Bash, e segue algumas regras claras. Não há bug aqui. Um ensaio relacionado sobre nomes de arquivos no shell também é muito revelador, pois aborda a questão do espaço em branco manipulação no Bash.

    
por 13.04.2018 / 09:15