Não é apenas eco vs printf
Primeiro, vamos entender o que acontece com a read a b cparte. readexecutará a divisão de palavras com base no valor padrão da IFSvariá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 bashmanual (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.txtpara 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á, readestaria tentando ajustar cada palavra individual em linevariá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
echofaz o que você esperaria aqui. Ele exibe suas variáveis exatamente como as readorganizou. 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 $cimprime, 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 $cvariável é citada, agora é reconhecida como uma sequência inteira 3 4e não se ajusta ao %dformato, 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=execvee 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, bashtem seu próprio built-in printf, que é o que você está usando em seu comando, stracena verdade chamará /usr/bin/printfversã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 printfsintaxe é 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 printfvs 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 printfeco. 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).
stracecaso e o outro -strace printfestá usando/usr/bin/printf, enquantoprintfdiretamente 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%qe, em novas versões,$()Tpara formatação de horário.