Com as variáveis ​​do Bash, qual é a diferença entre $ myvar e "$ myvar"? (Comportamento ímpar específico)

2

Pergunta Geral:

No Bash, eu 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 é por causa de como echofunciona. Em outros utilitários Unix, se as palavras estão agrupadas com 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".

Gostaria de saber qual é o significado mais profundo dessa diferença. Mais importante, como posso visualizar exatamente o que será passado para o comando, para que eu possa ver se foi 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 de aspas duplas? O que exatamente o git-log está vendo depois que a variável é desreferenciada sem aspas duplas?

Mas agora veja isso:

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 as aspas duplas são desnecessárias, elas não são removidas, sã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 usando argparseisso apenas imprime todos os argumentos (como Bash os interpretou, portanto, com literais de aspas duplas em que Bash pensa que fazem parte do argumento e com palavras agrupadas ou não agrupadas como Bash achar melhor), e o argparsescript Python se comporta de maneira muito racional. Infelizmente, acho que argparsepode estar silenciosamente corrigindo um problema conhecido com o Bash e, assim, obscurecendo as coisas bagunçadas que o Bash está passando para ele. Isso é apenas um palpite, não faço 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 isso agora, antes que haja respostas: eu sei que talvez eu possa usar aspas simples ao redor da coisa toda e depois não escapar das aspas duplas. Na verdade, isso funciona um pouco melhor para o meu problema inicial usando o git-log, mas eu testei em alguns outros contextos e é praticamente igualmente imprevisível e não confiável. Algo estranho está acontecendo ao citar dentro de variáveis. Eu nem vou postar todas as coisas estranhas que aconteceram com aspas simples.

Edit 2 - Isso também não funciona: eu só tive 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

Portanto, ele não possui aspas, mas possui um caractere de barra invertida literal na string de data. Além disso, git log $mydatesem erros de cotação, o espaço de barra invertida na variável

SerMetAla
fonte
Este Q é apenas sobre o git? Ou espaço em branco?
Xen2050 13/04
@ Xen2050 Sinceramente, não tenho certeza se o problema está relacionado ao Git. Estou bastante certo de que se relaciona com o Bash. É possível que o Git tenha quebrado algo ou o argparse do Python tenha consertado algo, porque eles têm um comportamento divergente. Além disso, o valor que 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:

4

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 entre aspas). Isso resulta em um único argumento:

  • --format=foo bar

No entanto, quando as variáveis não citadas são expandidas, os resultados passam pela divisão de palavras, mas não entre aspas. Portanto, se sua variável contiver --format="foo bar", ela será expandida para estes argumentos:

  • --format="foo
  • bar"

Isso pode ser verificado usando:

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

... 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
    sys de importação
    para i, arg no enumerar (sys.argv):
        imprimir (i, "=", arg)
    

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

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 obtendo seu próprio argumento:

git log "${myvar[@]}"
gravidade
fonte
Aceitei esta resposta, é muito mais útil que a outra. Obrigado.
SerMetAla
Adicione isso no topo, porque é o cerne 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 do problema original. Obrigado.
SerMetAla
@SerMetAla Estou apenas curioso: qual é o problema específico usando simplesmente 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 eu aceitei, além de um trecho da sua resposta, idealmente anexado na parte superior desta 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 printfno próprio Bash (testei) ou Python (testei) ou Perl (não testei, confio nele por extrapolação). Essa resposta também enfatiza o que está essencialmente acontecendo no seu snippet útil: o Bash está adicionando aspas duplas de que precisa, então não devo digitá-lo.
SerMetAla
3
Acredito que já respondi isso em "Usar matrizes".
grawity
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:

Por que preciso de 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ê (desnecessariamente) adicionou aspas literais usando \"a atribuição de variável.

Desde o comando será submetido a repartição de palavras , gitreceberá três argumentos, log, --date-format:"%Y-%m%-de T%H", portanto, reclamando de não encontrar qualquer confirmação ou objeto nomeado T%H".


Qual é a abordagem correta?

Se você deseja manter os argumentos juntos, se esse argumento contiver espaço em branco, será necessário agrupar 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 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 gitaspas adicionais - se tudo o que você deseja é ver 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?

Como você incluiu novamente aspas literais no argumento (escapando-as), que são transformadas em, digamos, aspas "reais" quando você esquece de citar a variável no seu comando real. Eu digo "esqueça" porque o uso de variáveis ​​não citadas nos comandos do shell geralmente 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á bug aqui. Um ensaio relacionado sobre nomes de arquivos no shell também é muito revelador, pois aborda a questão do manuseio de espaços em branco no Bash.

slhck
fonte
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 como se não funcionasse, mas funciona. Além disso, não há outra maneira de fazê-lo funcionar.
SerMetAla
Sim, é claro que esse comando funciona quando você o digita diretamente. Apenas não funciona quando você atribui o argumento (formato da data) a uma variável que contém aspas literais e depois expande essa variável.
slhck
@SerMetAla A distinção crítica é entre aspas sintáticas (que contornam os dados e são o que você deseja) e aspas literais (que fazem parte dos dados e são o que você obtém depois de colocar as aspas no valor de uma variável).
Gordon Davisson