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

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

Pergunta Geral:

No Bash, sei que o uso da variável myvarpode 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 se deve à forma como echofunciona. Em outros utilitários Unix, o fato de as palavras estarem agrupadas entre aspas duplas fará uma enorme diferença:

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

Estou me perguntando qual é o significado mais profundo dessa diferença. Mais importante ainda, como posso ver exatamente o que será passado para o comando, para poder ver se ele está citado corretamente?

Exemplo ímpar específico:

Vou apenas escrever 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"

Eca, 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, serão interpretadas como caracteres de aspas literais se e somente se não forem necessárias.

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

Para tornar as coisas mais complexas, escrevi um script Python argparseque apenas imprime todos os argumentos (como o Bash os interpretou, portanto, com literais de aspas duplas onde o Bash pensa que são parte do argumento e com palavras agrupadas ou não agrupadas como Bash achar adequado), e o argparsescript Python se comporta de maneira muito racional. Infelizmente, acho argparseque pode estar resolvendo silenciosamente um problema conhecido com o Bash e, assim, obscurecendo as coisas confusas que o Bash está passando para ele. Isso é apenas um palpite, não tenho ideia. Talvez o git-log esteja estragando secretamente o que o Bash está passando para ele.

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

Obrigado.

Edição editada:Deixe-me dizer isto agora, antes que haja qualquer resposta: eu sei que possotalvezuse aspas simples em torno de tudo e não escape das aspas duplas. Na verdade, isso funciona um pouco melhor para o meu problema inicial usando o git-log, mas testei-o em alguns outros contextos e é igualmente imprevisível e não confiável. Algo estranho está acontecendo com a citação de variáveis ​​internas. Não vou nem postar todas as coisas estranhas que aconteceram com aspas simples.

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

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

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

Portanto, não há aspas envolvendo-o,masele tem um caractere literal de barra invertida na sequência de data. Além disso, git log $mydatesem erros de aspas, com barra invertida na variável.

Responder1

Abordagem diferente:

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 contra divisão). Isso resulta em um único argumento:

  • --format=foo bar

No entanto, quando não citadovariáveissão expandidos, os resultados passam por divisão de palavras, masnãoatravés de unquoting. Portanto, se sua variável contiver --format="foo bar", ela será expandida para estes argumentos:

  • --format="foo
  • bar"

Isso pode ser verificado usando:

  • printf '%s\n' $variável

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

  • #!/usr/bin/env perl
    para $i(0..$#ARGV) {
        imprimir ($i+1)." = ".$ARGV[$i]."\n";
    }
    
  • #!/usr/bin/env python3
    sistema de importação
    para i, arg em enumerar (sys.argv):
        imprimir(i, "=", arg)
    

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

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

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

git log "${myvar[@]}"

Responder2

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:

Por que preciso das aspas duplas? O que exatamente o git-log está vendo depois que a variável é desreferenciada sem aspas duplas?

Se você não usar aspas duplas $mydate, a variável será expandida literalmente 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ê adicionou (desnecessariamente) aspas literais usando \"na atribuição de variável.

Como o comando passará pordivisão de palavras, gitreceberá três argumentos, loge --date-format:"%Y-%m%-d, T%H"portanto, reclamando por 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ços em branco, será necessário colocá-lo entre aspas.Geralmente, sempre coloque as 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 gitserá $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 de aspas adicionais - se tudo o que você deseja é gitver um argumento, coloque-o 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:

Eca, por que a saída impressa tem aspas duplas agora?

Porque você incluiu novamenteliteralaspas no argumento (escapando-as), que são transformadas em, digamos, aspas “reais” quando você se esquece de citar a variável em seu comando real. Digo “esqueça” porque usar variáveis ​​sem aspas em comandos shell normalmente só causa problemas – e aqui está revertendo um erro que você cometeu ao especificar a variável em primeiro lugar.

PS: Eu sei que tudo isso é confuso, mas isso é Bash e segue algumas regras claras. Não há nenhum bug aqui. Aensaio relacionadosobre nomes de arquivos no shell também é muito revelador, pois aborda a questão do tratamento de espaços em branco no Bash.

informação relacionada