Aqui está uma série de casos onde echo $var
pode mostrar um valor diferente do que foi atribuído. Isso acontece independentemente de o valor atribuído ter "aspas duplas", 'aspas simples' ou não.
Como faço para que o shell defina minha variável corretamente?
Asteriscos
A saída esperada é /* Foobar is free software */
, mas em vez disso, recebo uma lista de nomes de arquivos:
$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...
Colchetes
O valor esperado é [a-z]
, mas às vezes recebo uma única letra!
$ var=[a-z]
$ echo $var
c
Feeds de linha (novas linhas)
O valor esperado é uma lista de linhas separadas, mas em vez disso, todos os valores estão em uma linha!
$ cat file
foo
bar
baz
$ var=$(cat file)
$ echo $var
foo bar baz
Múltiplos espaços
Eu esperava um cabeçalho de tabela cuidadosamente alinhado, mas em vez disso, vários espaços ou desaparecem ou são reduzidos em um!
$ var=" title | count"
$ echo $var
title | count
Abas
Eu esperava dois valores separados por tabulação, mas em vez disso, recebo dois valores separados por espaço!
$ var=$'key\tvalue'
$ echo $var
key value
var=$(cat file)
está bom, masecho "$var"
é necessário.Respostas:
Em todos os casos acima, a variável está configurada corretamente, mas não foi lida corretamente! A maneira certa é usar aspas duplas ao fazer referência :
Isso dá o valor esperado em todos os exemplos dados. Sempre cite referências de variáveis!
Por quê?
Quando uma variável não está entre aspas , ela:
Submeta a divisão do campo onde o valor é dividido em várias palavras em espaços em branco (por padrão):
Antes:
/* Foobar is free software */
Depois:
/*
,Foobar
,is
,free
,software
,*/
Cada uma dessas palavras sofrerá expansão do nome do caminho , onde os padrões são expandidos em arquivos correspondentes:
Antes:
/*
Depois:
/bin
,/boot
,/dev
,/etc
,/home
, ...Por fim, todos os argumentos são passados para echo, que os escreve separados por espaços simples , dando
em vez do valor da variável.
Quando a variável é citada, ela:
É por isso que você deve sempre citar todas as referências de variáveis , a menos que precise especificamente de divisão de palavras e expansão do nome do caminho. Ferramentas como shellcheck existem para ajudar e alertam sobre aspas ausentes em todos os casos acima.
fonte
$(..)
tiras seguindo os feeds de linha. Você pode usarvar=$(cat file; printf x); var="${var%x}"
para contornar isso.Você pode querer saber por que isso está acontecendo. Junto com a ótima explicação daquele outro cara , encontre uma referência de Por que meu script de shell se engasga com espaços em branco ou outros caracteres especiais? escrito por Gilles em Unix e Linux :
fonte
Além de outros problemas causados pela falta de citação,
-n
e-e
podem ser consumidosecho
como argumentos. (Apenas o primeiro é legal de acordo com a especificação POSIXecho
, mas várias implementações comuns violam a especificação e consomem-e
também).Para evitar isso, use em
printf
vez deecho
quando os detalhes importam.Portanto:
No entanto, a citação correta nem sempre salvará você ao usar
echo
:... Considerando que você vai economizar com
printf
:fonte
-e
/-n
/ não está aparecendo?" Podemos adicionar links a partir daqui conforme apropriado.-n
também ?-e
. O padrão paraecho
não especifica a saída quando seu primeiro argumento é-n
, tornando qualquer / todas as saídas possíveis válidas nesse caso; não existe tal disposição para-e
.aspas duplas do usuário para obter o valor exato. como isso:
e ele lerá seu valor corretamente.
fonte
echo $var
a saída depende altamente do valor daIFS
variável. Por padrão, ele contém espaço, tabulação e caracteres de nova linha:Isso significa que quando o shell está fazendo a divisão de campo (ou divisão de palavra), ele usa todos esses caracteres como separadores de palavras. Isso é o que acontece ao fazer referência a uma variável sem aspas duplas para ecoá-la (
$var
) e, portanto, a saída esperada é alterada.Uma maneira de evitar a divisão de palavras (além de usar aspas duplas) é definir
IFS
como nulo. Consulte http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :Definir como nulo significa definir como valor vazio:
Teste:
fonte
set -f
evitar globbingIFS
definido como nulo,echo $var
será expandido paraecho '/* Foobar is free software */'
e a expansão do caminho não será realizada dentro de strings entre aspas simples.mkdir "/this thing called Foobar is free software etc/"
verá que ele ainda se expande. É obviamente mais prático para o[a-z]
exemplo.[a-z]
exemplo.A resposta de ks1322 me ajudou a identificar o problema ao usar
docker-compose exec
:Se você omitir o
-T
sinalizador,docker-compose exec
adicione um caractere especial que interrompa a saída, vemos emb
vez de1b
:Com
-T
bandeira,docker-compose exec
funciona como esperado:fonte
Além de colocar a variável entre aspas, também se pode traduzir a saída da variável usando
tr
e convertendo espaços em novas linhas.Embora isso seja um pouco mais complicado, ele adiciona mais diversidade à saída, pois você pode substituir qualquer caractere como separador entre as variáveis da matriz.
fonte
tr
o contrário para criar matrizes de arquivos de texto.tr
necessário criar adequadamente / corretamente um array a partir de um arquivo de texto - você pode especificar qualquer separador que desejar definindo IFS. Por exemplo:IFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0')
funciona todo o caminho até o bash 3.2 (a versão mais antiga em grande circulação) e define corretamente o status de saída como falso se vocêcat
falhar. E se você quiser, digamos, tabulações em vez de novas linhas, basta substituir o$'\n'
por$'\t'
.arrayname=( $( cat file | tr '\n' ' ' ) )
, então isso está quebrado em várias camadas: está agregando seus resultados (então um se*
transforma em uma lista de arquivos no diretório atual) e funcionaria tão bem sem otr
( ou ocat
, por falar nisso; um poderia apenas usararrayname=$( $(<file) )
e seria quebrado da mesma forma, mas de forma menos ineficiente).