Por que preciso citar variável para if, mas não para echo?

22

Li que você precisa de aspas duplas para expandir as variáveis, por exemplo,

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi

funcionará como esperado, enquanto

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi

sempre dirá $test ok , mesmo se $test for nulo.

mas por que não precisamos de citações em echo $test ?

    
por CharlesB 21.02.2013 / 14:37

5 respostas

31

Você sempre precisa de aspas em torno de variáveis em todos os contextos lista , ou seja, a variável pode ser expandida para vários valores, a menos que você queira os 3 efeitos colaterais de deixar uma variável sem aspas.

Os contextos

list incluem argumentos para comandos simples como [ ou echo , o for i in <here> , atribuições a matrizes ... Existem outros contextos onde as variáveis também precisam ser citadas. O melhor é sempre citar variáveis, a menos que você tenha uma boa razão para não fazê-lo.

Pense na ausência de aspas (em contextos de lista) como o operador split + glob .

Como se echo $test fosse echo glob(split("$test")) .

O comportamento do shell é confuso para a maioria das pessoas porque na maioria das outras linguagens você coloca aspas em torno de strings fixas, como puts("foo") , e não em torno de variáveis (como puts(var) ) enquanto na shell é o contrário: tudo é string em shell, então colocar aspas em volta de tudo seria complicado, você echo test , você não precisa "echo" "test" . No shell, aspas são usadas para outra coisa: impedir algum significado especial de alguns caracteres e / ou afetar o comportamento de algumas expansões.

Em [ -n $test ] ou echo $test , o shell dividirá $test (em branco por padrão) e, em seguida, executará a geração de nome de arquivo (expanda todos os padrões * , '?' ... para a lista de arquivos correspondentes) e, em seguida, passe essa lista de argumentos para os comandos [ ou echo .

Novamente, pense nisso como "[" "-n" glob(split("$test")) "]" . Se $test estiver vazio ou contiver apenas espaços em branco (spc, tab, nl), então o operador split + glob retornará uma lista vazia, então [ -n $test ] será "[" "-n" "]" , que é um teste para verificar se "- n "é a string vazia ou não. Mas imagine o que teria acontecido se $test fosse "*" ou "= foo" ...

Em [ -n "$test" ] , [ é passado nos quatro argumentos "[" , "-n" , "" e "]" (sem as aspas), que é o que queremos.

Se é echo ou [ não faz diferença, é apenas que echo produz a mesma coisa se passou um argumento vazio ou nenhum argumento.

Veja também esta resposta a uma pergunta semelhante para obter mais detalhes sobre o comando [ e o [[...]] de construção.

    
por 21.02.2013 / 15:00
7

A resposta do @h3rrmiller é boa para explicar por que você precisa das aspas para o if [ / test ), mas eu diria que sua pergunta está incorreta.

Tente os seguintes comandos e você verá o que quero dizer.

export testvar="123    456"
echo $testvar
echo "$testvar"

Sem as aspas, a substituição de variáveis faz com que o segundo comando seja expandido para:

echo 123    456

e os vários espaços são recolhidos para um único:

echo 123 456

Com as aspas, os espaços são preservados.

Isso acontece porque quando você cita um parâmetro (seja esse parâmetro passado para echo , test ou algum outro comando), o valor desse parâmetro é enviado como um valor para o comando. Se você não citar, o shell faz a mágica normal de procurar espaços em branco para determinar onde cada parâmetro começa e termina.

Isso também pode ser ilustrado pelo programa C (muito simples) a seguir. Tente o seguinte na linha de comando (você pode querer fazê-lo em um diretório vazio para não arriscar a sobrescrever algo).

cat <<EOF >paramtest.c
#include <stdio.h>
int main(int argc, char **argv) {
  int nparams = argc-1; /* because 1 parameter means only the executable's name */
  printf("%d parameters received\n", nparams);
  return nparams;
}
EOF
cc -o paramtest paramtest.c

e depois ...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"

Depois de executar paramtest , $? manterá o número de parâmetros para os quais foi passado (e esse número será impresso).

    
por 21.02.2013 / 15:05
2

Isto é tudo sobre como o shell interpreta a linha antes de um programa ser executado.

Se a linha ler echo I am $USER , o shell a expandirá para echo I am blrfl e echo não terá ideia se a origem do texto é uma expansão literal ou variável. Da mesma forma, se uma linha ler echo I am $UNDEFINED , o shell expandirá $UNDEFINED para nada e os argumentos do echo serão I am , e esse é o fim dele. Como echo funciona bem sem argumentos, echo $UNDEFINED é completamente válido.

Seu problema com if não está com if , porque if apenas executa o programa e os argumentos o seguem e executa a then parte se o programa sair 0 (ou else parte, se houver um e o programa sair não- 0 ):

if /bin/true ; then echo True dat. ; fi
if fgrep -q blrfl /etc/passwd ; then echo Blrfl has an account. ; fi

Quando você usa if [ ... ] para fazer uma comparação, não está usando primitivos embutidos no shell. Na verdade, você está instruindo o shell a executar um programa chamado [ , que é um superconjunto muito pequeno de test(1) , que requer que seu último argumento seja ] . Ambos os programas saem de 0 se a condição de teste for verdadeira e 1 se não for o caso.

A razão pela qual alguns testes quebram quando uma variável é indefinida é porque test não vê que você está usando uma variável. Ergo, [ $UNDEFINED -eq 2 ] é interrompido porque, quando o shell terminar, todos os test dos argumentos serão -eq 2 ] , o que não é um teste válido. Se você fizesse isso com algo definido, como [ $DEFINED -ne 0 ] , isso funcionaria porque o shell o expandiria em um teste válido (por exemplo, 0 -ne 0 ).

Há uma diferença semântica entre foo $UNDEFINED bar , que se expande para dois argumentos ( foo e bar ) porque $UNDEFINED correspondia ao seu nome. Compare isso com foo "$UNDEFINED" bar , que expande para três argumentos ( foo , uma string vazia e 'bar). As citações forçam o shell a interpretá-las como um argumento se existe alguma coisa entre elas ou não.

    
por 21.02.2013 / 20:17
0

Sem as aspas $test pode se expandir para mais de uma palavra, então é necessário citá-las para não quebrar a sintaxe, pois cada comutador dentro do comando [ está esperando um argumento que é o que as citações fazem. seja o que for que $test se expanda em um argumento)

O motivo pelo qual você não precisa de aspas para expandir uma variável com echo é porque não está esperando um argumento. Ele simplesmente imprimirá o que você diz. Assim, mesmo que $test se expanda para 100 palavras, o eco continuará a imprimi-lo.

Dê uma olhada nas armadilhas do Bash

    
por 21.02.2013 / 14:48
0

Parâmetros vazios são removidos se não forem citados:

start cmd:> strace -e trace=execve echo foo $bar baz
execve("/usr/bin/echo", ["echo", "foo", "baz"], [/* 100 vars */]) = 0

start cmd:> strace -e trace=execve echo foo "$bar" baz
execve("/usr/bin/echo", ["echo", "foo", "", "baz"], [/* 100 vars */]) = 0

O comando chamado não vê que havia um parâmetro vazio na linha de comando do shell. Parece que [é definido para retornar 0 para -n com nada seguindo. Porquê.

As citações também fazem diferença para o eco em vários casos:

var='*'
echo $var
echo "$var"

var="foo        bar"
echo $var
echo "$var"
    
por 21.02.2013 / 14:57

Tags