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


Pergunta Geral:

No Bash, eu sei que usando 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 trabalho. Em outros utilitários Unix, as palavras agrupadas com aspas duplas farão 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, como posso ver exatamente o que será passado para o comando, para que eu possa ver se ele é citado corretamente?

Exemplo estranho 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"

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, elas não forem necessárias.

O que é o Git 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 agrupadas ou não agrupadas como Bash entende), e o Python argparse script se comporta de forma muito racional. Infelizmente, eu acho argparse pode estar silenciosamente corrigindo um problema conhecido com o Bash e, assim, obscurecendo as coisas bagunçadas que o Bash está passando para ele. Isso é só um palpite, não faço ideia. Talvez o git-log esteja secretamente estragando 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 isso agora, antes que haja alguma resposta: eu sei que posso talvez use aspas simples em torno da coisa toda e depois não escape das 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: Eu só tive essa 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

Então não tem aspas 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.


2


origem


Isso é sobre o git apenas? Ou espaço em branco? - Xen2050
@ Xen2050 Eu sinceramente não tenho certeza se o problema está relacionado com o Git. Tenho certeza de que se relaciona com Bash. É possível que o Git tenha quebrado algo, ou o argumento do Python tenha corrigido alguma coisa, porque eles têm um comportamento divergente. Além disso, o valor que eu realmente quero contém -, =,:, [espaço],%, e também aspas duplas ou aspas simples, portanto é possivelmente um valor muito difícil de usar. - SerMetAla


Respostas:


Abordagem diferente:

Quando você corre git log --format="foo bar", essas citações 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 não mencionado variáveis são expandidos, os resultados passam por divisão de palavras, mas não através de unquoting. Então, se sua variável contiver --format="foo bar", é expandido para esses 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
    por $ i (0 .. $ # ARGV) {
        print ($ i + 1). "=". $ ARGV [$ i]. "\ n";
    }
    
  • #! / usr / bin / env python3
    import sys
    para i, arg em enumerate (sys.argv):
        print (i, "=", arg)
    

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

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 argumento:

git log "${myvar[@]}"

4



Aceitei esta resposta, é muito mais útil do que a outra. Obrigado. - SerMetAla
Por favor, adicione isso no topo, porque é o ponto crucial da resposta: mydate="--date=format:%Y-%m-%d T%H" Isso não usa variáveis ​​de matriz (que são impressionantes) e enfatiza a solução de trabalho que altera apenas um caractere do código original do problema. Obrigado. - SerMetAla
@SerMetAla Estou apenas curioso: o que especificamente é o problema de simplesmente usar a abordagem que mostrei, ou seja, mydate="--date=format:%Y-%m-%d T%H" e git log "$mydate"? - slhck
@slhck Acho que a resposta ideal será essa resposta, que aceitei, além de um trecho de sua resposta, idealmente incluído no início dessa resposta. Na verdade, vou usar esse trecho da sua resposta, obrigado. Esta resposta contém uma explicação correta de como ver o que o git-log realmente verá, usando printf no Bash em si (eu testei) ou Python (eu testei) ou Perl (eu não testei, eu confio nele por extrapolação). Esta resposta também enfatiza o que está essencialmente acontecendo em seu snippet útil: Bash é adicionando a aspa dupla que precisa, então eu não deveria digitar. - SerMetAla
Eu acredito que já respondi isso em "Use arrays". - grawity


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 usa aspas duplas $mydate, a variável será expandida textualmente, e a linha de 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 de variáveis.

Como o comando será submetido divisão de palavras, git receberá três argumentos, log, --date-format:"%Y-%m%-d e T%H", portanto, reclamando 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 citações são removidas pelo shell antes de serem passadas para git.

Você simplesmente não precisa da citação adicional - se tudo o que você quer é git para ver um argumento, envolva 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:

Por que a saída impressa tem aspas duplas agora?

Porque você voltou a incluir literal cita no argumento (escapando deles), que são transformados, digamos, em citações "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. UMA ensaio relacionado Sobre nomes de arquivos no shell também é muito revelador, como ele aborda a questão da manipulação de espaço em branco no Bash.


2



Você disse algumas coisas verdadeiras, no entanto, o seguinte comando funciona e é o que eu quero: git log --date=format:"%Y-%m-%d T%H" Você disse isso como se não funcionasse, mas funciona. Além disso, não há outra maneira de fazê-lo funcionar. - SerMetAla
Sim, claro que o comando funciona quando você digita diretamente. Ele só não funciona quando você atribui pela primeira vez o argumento (formato de data) a uma variável que contém aspas literais e depois expande essa variável. - slhck
@SerMetAla A distinção crítica é entre citações sintáticas (que vão por aí dados, e é o que você quer) e citações literais (que são parte de os dados, e é o que você obtém depois de colocar as cotas no valor de uma variável). - Gordon Davisson