Pergunta Geral:
No Bash, sei que o uso da 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 se deve à forma como echo
funciona. 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 argparse
que 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 argparse
script Python se comporta de maneira muito racional. Infelizmente, acho argparse
que 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 $mydate
sem 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, git
receberá três argumentos, log
e --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 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 de aspas adicionais - se tudo o que você deseja é git
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?
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.