Different (supposedly) POSIX compliant shell environments yield varied results when asked to emit non-printable characters. But there is one method that works consistently across a wide variety of shells.
Sparked by this conversation.
Given
hex='\x40\x00\x40'
hex2='\\x40\\x00\\x40'
oct='\100\000\100'
oct2='\0100\0000\0100'
you get this:
dashᵃ | dashᵇ | ksh | bash | BusyBox sh | zsh | |
---|---|---|---|---|---|---|
echo -n "$hex" |
$hex¹ | $hex | $hex | $hex | $hex | @␀@ ¹ |
printf "$hex" ² |
$hex | $hex | @␀@ |
@␀@ |
@␀@ |
@␀@ |
printf '%b' "$hex" ³ |
$hex | $hex | $hex | @␀@ |
@␀@ |
@␀@ |
echo -n "$hex2" |
$hex | $hex | $hex2 | $hex2 | $hex2 | $hex |
printf "$hex2" |
$hex | $hex | $hex | $hex | $hex | $hex |
printf '%b' "$hex2" |
$hex | $hex | $hex | $hex | $hex | $hex |
echo -n "$oct" |
@␀@ |
@ |
$oct | $oct | $oct | \100␀\100 ¹ |
printf "$oct" * |
@␀@ |
@␀@ |
@␀@ |
@␀@ |
@␀@ |
@␀@ |
printf '%b' "$oct" |
@␀@ |
@ |
\100␀\100 |
@␀@ |
@␀@ |
\100␀\100 |
echo -n "$oct2" |
@␀@ |
@ |
$oct2 | $oct2 | $oct2 | @␀@ |
printf "$oct2" |
␈0␀0␈0 ¹ |
␈0␀0␈0 |
␈0␀0␈0 |
␈0␀0␈0 |
␈0␀0␈0 |
␈0␀0␈0 |
printf '%b' "$oct2" |
@␀@ |
@ |
@␀@ |
@␀@ |
@␀@ |
@␀@ |
- ᵃ These results are from running dash in an Ubuntu Docker container.
- ᵇ This is from running dash on my host system, where it apparently interpreted the octal sequence as a C style (zero-terminated) string.
- ¹ Italicized $var name stands for the unchanged literal value of the corresponding variable;
@␀@
represents the expected byte sequence;
␀
stands for the null character (00
) and␈
stands for the backspace character (08
);. - ² A POSIX-compliant
printf
is not supposed to recognize "hexadecimal character constants as defined in the ISO C standard". - ³ The
b
conversion specifier character is supposed to only convert\0ddd
octal sequences, according to the POSIX spec. - * The only one that yields consistent results —
printf "$oct"
.
The only approach that works consistently across all shells is using the octal escape sequence as the printf
format string (printf '\100\000\100'
).