According to this question "Using "while read…" in a linux script"
echo '1 2 3 4 5 6' | while read a b c;do echo "$a, $b, $c"; done
outcome:
1, 2, 3 4 5 6
but when I replace echo
with printf
echo '1 2 3 4 5 6' | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
outcome
1, 2, 3
4, 5, 6
Could someone please tell me what makes these two commands different?
Thanks~
Best Answer
It's not just echo vs printf
First, let's understand what happens with
read a b c
part.read
will perform word-splitting based on the default value ofIFS
variable which is space-tab-newline, and fit everything based on that. If there's more input than the variables to hold it, it will fit splitted parts into first variables, and what can't be fitted - will go into last. Here's what I mean:This is exactly how it is described in
bash
's manual (see the quote at the end of the answer).In your case what happens is that, 1 and 2 fit into a and b variables, and c takes everything else, which is
3 4 5 6
.What you also will see a lot of times is that people use
while IFS= read -r line; do ... ; done < input.txt
to read text files line by line. Again,IFS=
is here for a reason to control word-splitting, or more specifically - disable it, and read a single line of text into a variable. If it wasn't there,read
would be trying to fit each individual word intoline
variable. But that's another story, which I encourage you to study later, sincewhile IFS= read -r variable
is a very frequently used structure.echo vs printf behavior
echo
does what you'd expect here. It displays your variables exactly asread
has arranged them. This has been already demonstrated in previous discussion.printf
is very special, because it will keep on fitting variables into format string until all of them are exhausted. So when you doprintf "%d, %d, %d \n" $a $b $c
printf sees format string with 3 decimals, but there's more arguments than 3 (because your variables actually expand to individual 1,2,3,4,5,6). This may sound confusing, but exists for a reason as improved behavior from what the realprintf()
function does in C language.What you also did here that affects the output is that your variables are not quoted, which allows the shell ( not
printf
) to break down variables into 6 separate items. Compare this with quoting:Exactly because
$c
variable is quoted, it is now recognized as one whole string,3 4
, and it doesn't fit the%d
format, which is just a single integerNow do the same without quoting:
printf
again says: "OK, you have 6 items there but format shows only 3, so I'll keep fitting stuff and leaving blank whatever I cannot match to actual input from user".And in all these cases you don't have to take my word for it. Just run
strace -e trace=execve
and see for yourself what does the command actually "see":Additional notes
As Charles Duffy properly pointed out in the comments,
bash
has its own built-inprintf
, which is what you're using in your command,strace
will actually call/usr/bin/printf
version, not shell's version. Aside from minor differences, for our interest in this particular question the standard format specifiers are the same and behavior is the same.What also should be kept in mind is that
printf
syntax is far more portable ( and therefore preferred ) thanecho
, not to mention that the syntax is more familiar to C or any C-like language that hasprintf()
function in it. See this excellent answer by terdon on the subject ofprintf
vsecho
. While you can make the output tailored to your specific shell on your specific version of Ubuntu, if you are going to be porting scripts across different systems, you probably should preferprintf
rather than echo. Maybe you're a beginner system administrator working with Ubuntu and CentOS machines, or maybe even FreeBSD - who knows - so in such cases you will have to make choices.Quote from bash manual, SHELL BUILTIN COMMANDS section