5

The ASCII hex code for a zero is 0x30. Hence, you can print a zero by doing printf '\x30', and it will print a zero.

If you put this into a shell script called myScript.sh, and then execute ./myScript.sh, it will also print a zero.

But if you execute sh -c printf '\x30' or alternatively, sh myScript.sh, you will instead get the literal characters "\x30", rather than having it be interpreted as a single byte.

Why is that?

(Behavior has been observed on multiple machines, all of which I believe are running bash).

Quasímodo
  • 18,865
  • 4
  • 36
  • 73
Murphy
  • 61
  • 1
    Somewhat related Bash printf formating not working . IIRC octal escapes should be portable - try \060 – steeldriver Dec 31 '20 at 22:32
  • 1
    If you say sh myScript.sh, then you have explicitly told sh (i.e. dash) to execute the text of your script itself. The shebang (because it starts with #) is just a comment in that case. The #! is a "magic number" which is only actioned when the file containing it is executed via the execve() system call. – Paul_Pedant Dec 31 '20 at 23:24

2 Answers2

7

The implementation of printf in some shells (bash, ksh, zsh, at least; even in sh emulation mode) understands \xHH as the hexadecimal number HH. This is however an extension to the standard printf specification.

What's required by the POSIX standard is that printf recognize \0ddd, where ddd is a zero, one, two, or three digit octal number.

The shell that stands in for /bin/sh on your system is most likely dash (or, less likely, yash), which understands octal (like all standard shells), but does not understand hexadecimal, when used with printf.

The hexadecimal number 30 is 60 in octal, so

$ sh -c 'printf "%b\n" "$1"' sh '\060'
0

Or, if you don't want to pass the octal number as an argument but instead want to use it as a static literal:

$ sh -c 'printf "\060\n"'
0

(Note the difference here to the sh -c statement in your question: the whole printf statement, including its arguments, are part of the string given as the option-argument to the sh utility's -c option. I'm assuming what you have in your question is a simple typo.)

The reason your script managed to print a zero correctly is either because you had a #!-line in the script that pointed to the bash shell executable, or you had no such line at all and instead ran the script from an interactive bash shell. The bash shell, when executing a shell script with no #! line, will use bash to run it (see Which shell interpreter runs a script with no shebang? for more about this detail).

When you run the script with an explicit interpreter, as in sh myScript.sh, the #! line, if there is one, will be ignored.

Kusalananda
  • 333,661
  • Note: POSIX printf recognizes the \0ddd form (with the 0) only in arguments interpreted by the b specifier, not in the format string. In the format string, it recognizes the \ddd form (without the 0). For example, these are equivalent: /usr/bin/printf '%b' '\0060', /usr/bin/printf '%b' '\060', /usr/bin/printf '\060'. But this is not: /usr/bin/printf '\0060'. A hex dump of the last one will show that it results in two bytes: 0x06 0x30. – Robin A. Meade Mar 22 '22 at 00:24
1

When I execute

$ sh -c printf '\x30'

I get

printf: usage: printf [-v var] format [arguments]

which already provides a precious hint that printf doesn't get the expected parameters. The solution is to make sure the command parameter is a single string, not multiple parameters, by enclosing it in quotes. And of course adding a newline for readability doesn't hurt:

$ sh -c "printf '\x30\n'"
0
Murphy
  • 2,649