1

On OSX High Sierra, bash's printf seems to behave erroneously. Consider:

printf "[%s]" "x"

returns

[x]

all good... but:

printf "[%s]" "x" "y"

returns

[x][y]

instead of just [x] !!

don't tell me: don't provide more parameters. I don't know what the format will look like as it's passed to me, but I have parameters

the docs don't address this squarely, merely stating:

The format string is reused as often as necessary to satisfy the arguments. Any extra format specifications are evaluated with zero or the null string.

is this broken?

Gonzalo Matheu
  • 8,984
  • 5
  • 35
  • 58
ekkis
  • 9,804
  • 13
  • 55
  • 105
  • 4
    It's not a bug but a feature – oguz ismail Mar 27 '19 at 18:00
  • What do you mean "it's passed to me"? It isn't in this example. Can you show what you're dealing with? – tadman Mar 27 '19 at 18:00
  • 2
    This isn't a MacOs specific feature .. This is a `bash` feature in general .. – Zak Mar 27 '19 at 18:02
  • 3
    "*The format string is reused as often as necessary to satisfy the arguments.*" is exactly why this happens. By the time `printf` reaches the end of your format string, there are still some unused arguments left, so `printf` starts over from the beginning of the format string. – melpomene Mar 27 '19 at 18:03
  • @melpomene, I was thinking it would behave like the original printf from C! – ekkis Mar 30 '19 at 05:38

2 Answers2

3

From posix utilities printf:

  1. The format operand shall be reused as often as necessary to satisfy the argument operands.

That exactly means that the format string is repeated as many times it needs to go through all the arguments. This is exactly how it was intended to work and this is one of the most useful features of printf.

You want to repeat a character '#' 10 times? Nothing simpler:

printf "#%.0s" $(seq 10)
# will expand to:
printf "#%.0s" 1 2 3 4 5 6 7 8 9 10
# is equivalent to:
printf "#%.0s#%.0s#%.0s#%.0s#%.0s#%.0s#%.0s#%.0s#%.0s#%.0s" 1 2 3 4 5 6 7 8 9 10

The %.0s will print zero character from the string, so it will print zero character, so it will.. print nothing. Thus the # is repeated as many times as many arguments are there.

You have an array and want to print all array members separated with a newline? Nothing simpler:

arr=(1 2 3 value1 test5 text7)
printf "%s\n" "${arr[@]}"
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • wow. ok. it's counterintuitive but I can see how this is valuable. it also, unfortunately, means that I cannot have extra parameters, so if I get a format string I now need to go count the references in it and make sure to pass the right number of parameters. it would have been easier if it worked like the orginal printf in C where it just ignores extra parameters – ekkis Mar 30 '19 at 05:36
2

From my understanding is behaving as stated in this sentence of documentation:

The format string is reused as often as necessary to satisfy the arguments.

In your case, you have 2 arguments ("y" and "z") and just 1 format string ([%s]), so it is reused (i.e: use the same for each argument).

It iterates the arguments list and when it reaches the format string list end, it starts from the beginning:

The command:

printf "[%s](%s)" "x" "y" "z" "a" 

Ouputs:

[x](y)[z](a)
Gonzalo Matheu
  • 8,984
  • 5
  • 35
  • 58
  • I took that sentence to mean that `printf "%s %s" "a"` would print "a a", which is the reverse – ekkis Mar 30 '19 at 05:33
  • 1
    @ekkis That's covered by the second part: "*Any extra format specifications are evaluated with zero or the null string.*" If you have too few arguments for the format string, they're assumed to be 0 (for numbers) or the empty string (for %s). – melpomene Mar 30 '19 at 10:29