0

I was messing around and forgot to delete a section of a printf statement and got this output:

# printf '%*s%c' {A..Z}
bash: printf: A: invalid number
bash: printf: D: invalid number
bash: printf: G: invalid number
bash: printf: J: invalid number
bash: printf: M: invalid number
bash: printf: P: invalid number
bash: printf: S: invalid number
bash: printf: V: invalid number
bash: printf: Y: invalid number
BCEFHIKLNOQRTUWXZ

Why are BCEFHIKLNOQRTUWXZ considered valid numbers? I thought it was because they could be format characters, but that doesn't seem to explain Z.

Is there somewhere I can read/learn more about this?

System Info:

  • GNU bash, version 4.4.12(1)-release (x86_64-unknown-linux-gnu)
  • @StéphaneChazelas. GNU bash, version 4.4.12(1)-release (x86_64-unknown-linux-gnu) – Grayson Kent Aug 28 '17 at 12:26
  • I am stupid. I pored through history again and realized I actually executed printf '%*s%c' {A..Z} so that is working as expected. Sorry for the trouble. If any of you want to write up the answer, I'll accept it and close – Grayson Kent Aug 28 '17 at 12:39
  • @StéphaneChazelas Done. Thank you for your help – Grayson Kent Aug 28 '17 at 12:48
5

The %*s format specification consumes two arguments like in C, one for the padding width and one for the string to be padded.

The printf utility keeps reusing the format if there are arguments remaining after the previous rounds have consumed them:

That's to be used for instance as:

$ printf '%*s%c' 3 x1 '|' 4 y1  '|' 3 z1 "$NL" \
                 3 x2 '|' 4 y22 '|' 3 z2 "$NL"
 x1|  y1| z1
 x2| y22| z2

To format output in columns. Now, for what's expected as those width arguments, the behaviour depends on the printf implementation.

  • For the builtin of zsh and AT&T ksh, that can be any arithmetic expression (like in most places that expect a number). So, 1+1 or 2 mean the same. A as an arithmetic expression resolves to the value of $A, or 0 is $A is unset or empty.
  • GNU printf and the printf builtin of bash and dash only accept decimal, octal or hexadecimal literal constants (so 020, 16 and 0x10 mean the same) with leading (but not trailing) blanks ignored and return an error message (but otherwise use whatever valid number if found at the start of the argument or 0 if none) if it's not recognised as a valid number.
  • yash's printf builtin currently doesn't support %*s

So:

printf '%*s%c' A B C

Would output BC in ksh and zsh unless $A contains an arithmetic expression that resolves to a number with an integer part other than 0.

$ A=5.2*2 zsh -c "printf '%*s%c' A B C"
         BC

And output BC in bash/dash/GNU but also an error message as A is not a valid decimal/octal/hexadecimal literal constant.

zsh or ksh could output error messages if the arithmetic expression is invalid, and like everywhere an arithmetic expression is evaluated, could also run arbitrary commands with the wrong variables in the environment:

$ A=1+ zsh -c "printf '%*s%c' A B C"
zsh:1: bad math expression: operand expected at end of string
BC
$ A='psvar[0$(uname>&2)]' zsh -c "printf '%*s%c' A B C"
Linux
BC

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.