Não é apenas eco vs printf
Primeiro, vamos entender o que acontece com a read a b c
parte. read
executará a divisão de palavras com base no valor padrão da IFS
variável que é space-tab-newline e ajustará tudo com base nisso. Se houver mais informações do que as variáveis para mantê-lo, ele encaixará partes divididas nas primeiras variáveis, e o que não pode ser ajustado - será o último. Aqui está o que eu quero dizer:
bash-4.3$ read a b c <<< "one two three four"
bash-4.3$ echo $a
one
bash-4.3$ echo $b
two
bash-4.3$ echo $c
three four
É exatamente assim que é descrito no bash
manual (veja a citação no final da resposta).
No seu caso, o que acontece é que, 1 e 2 se encaixam nas variáveis aeb, ec toma tudo o que é 3 4 5 6
.
O que você também verá muitas vezes é que as pessoas usam while IFS= read -r line; do ... ; done < input.txt
para ler arquivos de texto linha por linha. Novamente, IFS=
aqui está um motivo para controlar a divisão de palavras, ou mais especificamente - desative-a e leia uma única linha de texto em uma variável. Se não estivesse lá, read
estaria tentando ajustar cada palavra individual em line
variável. Mas essa é outra história, que eu encorajo você a estudar mais tarde, pois while IFS= read -r variable
é uma estrutura usada com muita frequência.
comportamento de eco vs printf
echo
faz o que você esperaria aqui. Ele exibe suas variáveis exatamente como as read
organizou. Isso já foi demonstrado na discussão anterior.
printf
é muito especial, porque continuará ajustando variáveis na string de formatação até que todas elas estejam esgotadas. Então, quando você printf "%d, %d, %d \n" $a $b $c
imprime, vê uma string de formato com três casas decimais, mas há mais argumentos que três (porque suas variáveis realmente se expandem para 1,2,3,4,5,6). Isso pode parecer confuso, mas existe por uma razão como um comportamento aprimorado do que a função real printf()
faz na linguagem C.
O que você também fez aqui que afeta a saída é que suas variáveis não são citadas, o que permite que o shell (não printf
) divida as variáveis em 6 itens separados. Compare isso com a citação:
bash-4.3$ read a b c <<< "1 2 3 4"
bash-4.3$ printf "%d %d %d\n" "$a" "$b" "$c"
bash: printf: 3 4: invalid number
1 2 3
Exatamente porque a $c
variável é citada, agora é reconhecida como uma sequência inteira 3 4
e não se ajusta ao %d
formato, que é apenas um único número inteiro.
Agora faça o mesmo sem citar:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
printf
novamente diz: "OK, você tem 6 itens lá, mas o formato mostra apenas 3, então continuarei ajustando as coisas e deixando em branco o que não puder corresponder à entrada real do usuário".
E em todos esses casos, você não precisa aceitar minha palavra. Basta executar strace -e trace=execve
e ver por si mesmo o que o comando realmente "vê":
bash-4.3$ strace -e trace=execve printf "%d %d %d\n" $a $b $c
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3", "4"], [/* 80 vars */]) = 0
1 2 3
4 0 0
+++ exited with 0 +++
bash-4.3$ strace -e trace=execve printf "%d %d %d\n" "$a" "$b" "$c"
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3 4"], [/* 80 vars */]) = 0
1 2 printf: ‘3 4’: value not completely converted
3
+++ exited with 1 +++
Notas Adicionais
Como Charles Duffy apontou corretamente nos comentários, bash
tem seu próprio built-in printf
, que é o que você está usando em seu comando, strace
na verdade chamará /usr/bin/printf
versão, não a versão do shell. Além de pequenas diferenças, para nosso interesse nessa questão em particular, os especificadores de formato padrão são os mesmos e o comportamento é o mesmo.
O que também deve ser lembrado é que a printf
sintaxe é muito mais portátil (e, portanto, preferida) do que echo
, sem mencionar que a sintaxe é mais familiar para C ou qualquer linguagem semelhante a C que tenha printf()
função nela. Veja este excelente resposta por terdon sobre o tema da printf
vs echo
. Embora você possa fazer a saída adaptada ao seu shell específico na sua versão específica do Ubuntu, se você estiver portando scripts em diferentes sistemas, provavelmente prefere o printf
eco. Talvez você seja um administrador de sistemas iniciante trabalhando com máquinas Ubuntu e CentOS, ou talvez até FreeBSD - quem sabe - então, nesses casos, você terá que fazer escolhas.
Cite o manual do bash, seção SHELL BUILTIN COMMANDS
leia [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [nome ... ]
Uma linha é lida a partir da entrada padrão ou do descritor de arquivo fd fornecido como argumento para a opção -u, e a primeira palavra é atribuída ao primeiro nome, a segunda palavra ao segundo nome e assim por diante, com as sobras palavras e seus separadores intermediários atribuídos ao sobrenome. Se houver menos palavras lidas no fluxo de entrada do que nomes, os nomes restantes receberão valores vazios. Os caracteres no IFS são usados para dividir a linha em palavras usando as mesmas regras que o shell usa para expansão (descritas acima em Separação de palavras).
strace
caso e o outro -strace printf
está usando/usr/bin/printf
, enquantoprintf
diretamente no bash está usando o shell incorporado com o mesmo nome. Eles nem sempre serão idênticos - por exemplo, a instância do bash possui especificadores de formato%q
e, em novas versões,$()T
para formatação de horário.