Não é apenas eco vs printf
Primeiro, vamos entender o que acontece com read a b c
part. read
executará a divisão de palavras com base no valor padrão de IFS
variable que é space-tab-newline e ajustará tudo com base nisso. Se houver mais entrada do que as variáveis para segurá-la, ela encaixará as partes divididas em primeiras variáveis, e o que não pode ser ajustado - entrará no ú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 manual do bash
(veja a citação no final da resposta).
No seu caso, o que acontece é que 1 e 2 se encaixam nas variáveis aeb, e c pega todo o resto, 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=
está aqui por um motivo para controlar a divisão de palavras ou, mais especificamente, desativá-lo e ler uma única linha de texto em uma variável. Se não estivesse lá, read
estaria tentando encaixar cada palavra individual na variável line
. Mas essa é outra história, que eu encorajo você a estudar mais tarde, já que while IFS= read -r variable
é uma estrutura usada com frequência.
comportamento echo vs printf
echo
faz o que você espera aqui. Ele exibe suas variáveis exatamente como read
as organizou. Isso já foi demonstrado em discussões anteriores.
printf
é muito especial, porque continuará ajustando variáveis na string de formato até que todas estejam esgotadas. Então, quando você faz printf "%d, %d, %d \n" $a $b $c
printf vê a string de formato com 3 casas decimais, mas há mais argumentos do que 3 (porque suas variáveis realmente se expandem para individuais 1,2,3,4,5,6). Isso pode parecer confuso, mas existe por um motivo como um comportamento melhorado do que a função real printf()
faz na linguagem C.
O que você também fez aqui e 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 as citações:
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 variável $c
está entre aspas, agora ela é reconhecida como uma string inteira, 3 4
e não cabe no formato %d
, que é apenas um único 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 eu continuarei ajustando o material e deixando em branco o que eu não puder corresponder à entrada real do usuário".
E em todos esses casos você não precisa acreditar na 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 printf
, que é o que você está usando em seu comando, strace
na verdade chama /usr/bin/printf
version, não shell versão. Além de pequenas diferenças, para nosso interesse nesta 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 mantido é que a sintaxe printf
é muito mais portátil (e portanto preferida) do que echo
, sem mencionar que a sintaxe é mais familiar a C ou qualquer linguagem semelhante a C que possua printf()
função nele. Veja esta excelente resposta por terdon sobre o assunto de printf
vs echo
. Embora você possa fazer a saída feita sob medida para o seu shell específico em sua versão específica do Ubuntu, se você estiver portando scripts em sistemas diferentes, provavelmente deve preferir printf
em vez de eco. Talvez você seja um administrador de sistema iniciante que trabalha com máquinas Ubuntu e CentOS, ou talvez até com o FreeBSD - quem sabe - então, nesses casos, você terá que fazer escolhas.
Citação do manual do bash, seção SHELL BUILTIN COMMANDS
ler [-ers] [-um aname] [-d delim] [-i texto] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [nome ...]
Uma linha é lida da entrada padrão, ou do descritor de arquivo fd fornecido como um argumento para a opção -u, e a primeiro palavra é atribuída ao primeiro nome, a segunda palavra ao segundo nome, e assim por diante, com as palavras restantes e sua intervenção separa‐ atribuídos ao último nome. Se houver menos palavras lidas no fluxo de entrada do que nomes, os nomes restantes serão atribuído 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 (descrito acima no Word Splitting).