Why does printf print more arguments than expected?

You have three problems:

  1. With read, if there are fewer variable names than fields in the input, the last var will be bound to all the remaining fields on the line, with delimiters. That means that $e gets 5 6 in your first unexpected example.
  2. Because all of $a..$e are unquoted, their values undergo field splitting. If $e holds "5 6" then it expands into two arguments to the command.
  3. printf consumes all its arguments, using as many arguments at once as there are % substitutions, repeatedly. This is buried in the documentation as:

    The format operand shall be reused as often as necessary to satisfy the argument operands. Any extra c or s conversion specifiers shall be evaluated as if a null string argument were supplied; other extra conversion specifications shall be evaluated as if a zero argument were supplied.

    In other words, if there are unused arguments it starts over again and processes them from the beginning too, including the whole format string. This is useful when you want to format an entire array, say:

    printf '%b ' "${array[@]}"

    Your printf command gets one argument from each of $a..$d, and then however many are left over from $e. When $e is "5 6", printf has two goes around, the second just getting 6 to format. When it's 5 6 7 8 9 10 it has the full range of substitutions for the second printing.

You can avoid all of these by adding an extra dummy field to read, and quoting your parameter substitutions (which is always a good idea):

read  a b c d e dummy
printf "> %s %s %s %s %s <" "$a" "$b" "$c" "$d" "$e"

This will give:

Enter 5 words : 
1 2 3 4 5 6 7 8 9 10
> 1 2 3 4 5 <

dummy gets all the extra fields, and printf only gets the five arguments you expected.

Your second edited-in question has a similar answer: only a gets a value when IFS doesn't have a space. That means $b..$e expand to nothing, so printf only gets a single argument. Your spaces from the format string are printed, with nothing substituted in between them ("as if a null string argument were supplied").

 printf "> %s < " 1 2 3

will print

 > 1 <> 2 <> 3 <

  printf "> %s %s <" 1 2 3


 > 1 2 <> 3  <

printf eats up all the arguments to satisfy its format string and then repeats until all arguments are processed.

The second scripts works because only $a is ever assigned to and therefore the command doesn't overflow into additional iterations (there's only ever one iteration).

This behavior is documented in the text provided with help printf:

... The format is re-used as necessary to consume all of the arguments. If there are fewer arguments than the format requires, extra format specifications behave as if a zero value or null string, as appropriate, had been supplied. ...

and is mandated by http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html


Shell Script