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

26

Eu li que você precisa de aspas duplas para expandir 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 okmesmo se $testfor nulo.

mas então por que não precisamos de aspas echo $test?

CharlesB
fonte
2
Se você não citar uma variável a ser usada como argumento echo, espaços extras e novas linhas serão removidos.
Jordanm

Respostas:

36

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

Os contextos de lista incluem argumentos para comandos simples como [ou echo, as for i in <here>atribuições a matrizes ... Existem outros contextos em que 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 $testfosse 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 cadeias fixas, como puts("foo"), e não em torno de variáveis ​​(como puts(var)), enquanto no shell é o contrário: tudo é string em shell, colocando aspas em tudo seria complicado, você echo test, você não precisa "echo" "test". No shell, as 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 é dividido $test(em branco por padrão) e, em seguida, executa a geração do nome do arquivo (expanda todos os *padrões '?' ... para a lista de arquivos correspondentes) e passa essa lista de argumentos para os comandos [ou echo.

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

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

Se é echoou [não faz diferença, é exatamente isso que echoproduz a mesma coisa, seja passado um argumento vazio ou nenhum argumento.

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

Stéphane Chazelas
fonte
7

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

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

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

Sem as aspas, a substituição da variável faz com que o segundo comando se expanda para:

echo 123    456

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

echo 123 456

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

Isso acontece porque quando você cita um parâmetro (se o parâmetro é passado para echo, testou algum outro comando), o valor desse parâmetro é enviado como um valor para o comando. Se você não citar, o shell faz sua 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 seguinte programa C (muito, muito simples). Tente o seguinte na linha de comando (você pode fazê-lo em um diretório vazio para não arriscar substituir 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"

Após a execução paramtest, $?manterá o número de parâmetros que foram passados ​​(e esse número será impresso).

um CVn
fonte
2

É tudo sobre como o shell interpreta a linha antes que um programa seja executado.

Se a linha for lida echo I am $USER, o shell a expande echo I am blrfle echonão tem idéia 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 se expandirá $UNDEFINEDem nada e os argumentos do eco serão I am, e é o fim disso. Como echofunciona muito bem sem argumentos, echo $UNDEFINEDé completamente válido.

Seu problema com ifnão está realmente certo if, porque ifapenas executa o programa e os argumentos que o seguem e executa a thenparte se o programa sair 0(ou a elseparte 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ê if [ ... ]faz uma comparação, não usa primitivas embutidas no shell. Você está realmente instruindo o shell a executar um programa chamado [que é um superconjunto muito pequeno do test(1)que requer seu último argumento ]. Ambos os programas são encerrados 0se a condição de teste for verdadeira e 1se não.

A razão pela qual alguns testes são interrompidos quando uma variável é indefinida é porque testvocê não vê que está usando uma variável. Ergo, [ $UNDEFINED -eq 2 ]quebras porque, quando o shell termina, tudo testindica argumentos -eq 2 ], o que não é um teste válido. Se você fizesse isso com algo definido, como, por exemplo [ $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 ( fooe bar) porque $UNDEFINEDcorrespondeu ao seu nome. Compare isso com foo "$UNDEFINED" bar, que se expande para três argumentos ( foo, uma string vazia e `bar). As aspas forçam o shell a interpretá-las como um argumento sobre se há algo entre elas ou não.

Blrfl
fonte
0

Sem aspas $testpode expandir para ser mais de uma palavra, portanto, precisa ser citada para não quebrar a sintaxe, pois cada opção dentro do [comando espera um argumento que é o que as aspas fazem (faz o que se $testexpande para um argumento)

O motivo pelo qual você não precisa de aspas para expandir uma variável echoé porque ela não está esperando um argumento. Simplesmente imprimirá o que você mandou. Portanto, mesmo que se $testexpanda para 100 palavras, o eco ainda o imprimirá.

Dê uma olhada no Bash Pitfalls

h3rrmiller
fonte
sim, mas por que não precisamos disso echo?
precisa
@CharlesB Você precisa das cotações para echo. O que faz você pensar o contrário?
Gilles 'SO- stop be evil'
Eu não preciso deles, eu posso echo $teste ele funciona (ele gera o valor de $ test)
charlesb
1
@CharlesB Ele gera apenas o valor de $ test se isso não contiver vários espaços em qualquer lugar. Experimente o programa na minha resposta para ilustrar o motivo.
um CVn 22/02
0

Os parâmetros vazios são removidos se não estiverem 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 [está definido para retornar 0 para -n sem nada a seguir. Por que.

A citação também faz diferença para o eco em vários casos:

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

var="foo        bar"
echo $var
echo "$var"
Hauke ​​Laging
fonte
2
Não é echo, é a concha. Você veria o mesmo comportamento com ls. Tente touch '*'algum tempo se você se sentir aventureiro. :)
a CVn
Isso é apenas uma redação, pois não há diferença para o caso 'se [...] `. [não é um comando shell especial. Isso é diferente de [[(no bash), onde a citação não é necessária.
Hauke ​​Laging