printf in shell script can't do \x%x

5,038

\ is used several times in there:

  • for the `...` form of command substitution. Best is to use $(...).
  • to escape characters like $, ` and \ inside double quotes. Best to use single quotes instead.
  • to escape \ in the format argument of printf
  • to introduce that \xHH sequence in the format argument of the other printf (not standard though).

So it should either be:

printf `printf "\\\\\\\\x%x" 255 255 255 0`
printf `printf '\\\\x%x' 255 255 255 0`
printf $(printf '\\x%x' 255 255 255 0)

That is, you need to pass \\ to the rightmost printf for it to output \, but with `...`, you'd need to escape each \ with \, and do that again for "...".

That's still invoking the split+glob operator which we don't want here. So:

printf "$(printf '\\x%x' 255 255 255 0)"

Or portably:

printf "$(printf '\\%o' 255 255 255 0)"

With some awk implementations (not all with work with 0):

LC_ALL=C awk 'BEGIN{printf "%c%c%c%c", 255, 255, 255, 0}'

With perl:

perl -e 'print pack "C*", @ARGV' 255 255 255 0

zsh alternative that avoids forking a subshell:

(){setopt localoptions nomultibyte; printf %s ${(#)@}} 255 255 255 0

bash alternative that avoids forking a subshell (also works in recent versions of zsh):

printf -v x '\\%o' 255 255 255 0
printf "$x"
Share:
5,038

Related videos on Youtube

Paul Wratt
Author by

Paul Wratt

Updated on September 18, 2022

Comments

  • Paul Wratt
    Paul Wratt over 1 year

    where ECHO-VAR produces \xFF\xFF\xFF\x00 ($fb_COLOR15) these work on the command line:

    CP="`ECHO-VAR`" printf $CP | dd status=none bs=4 count=$(( ( 1360 * 100 ) + 100 )) > /dev/fb0
    printf "`ECHO-VAR`" | dd status=none bs=4 count=$(( ( 1360 * 100 ) + 100 )) > /dev/fb0
    printf "${fb_COLOR15}" | dd status=none bs=4 count=$(( ( 1360 * 100 ) + 100 )) > /dev/fb0
    

    but they don't work in a shell script (#!/bin/sh). It will only output \xFF\xFF\xFF\x00 or xFFxFFxFFx00 instead of four (4) characters, even if piped through sed 's/\\/\\\\\\\\\\/g'.

    these work but with bash: printf: missing hex digit for \x (x4), they produce what you expect at the end:

    printf `printf "\x%x\x%x\x%x\x%x" 255 255 255 0` | dd status=none bs=4 count=$(( ( 1360 * 100 ) + 100 )) > /dev/fb0
    printf "`printf "\\x%x\\x%x\\x%x\\x%x" 255 255 255 0`" | dd status=none bs=4 count=$(( ( 1360 * 100 ) + 100 )) > /dev/fb0
    printf "`printf "\\\x%x\\\x%x\\\x%x\\\x%x" 255 255 255 0`" | dd status=none bs=4 count=$(( ( 1360 * 100 ) + 100 )) > /dev/fb0
    printf "`printf "\\\\x%x\\\\x%x\\\\x%x\\\\x%x" 255 255 255 0`" | dd status=none bs=4 count=$(( ( 1360 * 100 ) + 100 )) > /dev/fb0
    

    however this works (five slashes):

    printf "`printf "\\\\\x%x\\\\\x%x\\\\\x%x\\\\\x%x" 255 255 255 0`" | dd status=none bs=4 count=$(( ( 1360 * 100 ) + 100 )) > /dev/fb0
    

    but not in a shell script (#!/bin/sh), it produces the same error.

    I tried various combinations of ` execution, layered echo's and printf's for 5 hours before changing the input format from \x type to the four decimal argument format, which still failed.


    FYI: they will produce a white dot on a 32bit framebuffer.

    In 5 minutes I got it to work from both the command line and in a shell script using:

    bas d2a.bas 255 255 255 0 | dd status=none bs=4 count=$(( ( 1360 * 100 ) + 100 )) > /dev/fb0
    

    d2a.bas:

     1 rem D2A.BAS - decimal arguments to ASCII characters
     10 for i=1 to 255
     20   a$=command$(i)
     30   if a$="" then
     40     i=255
     50   else
     60     a=val(A$)
     70     print chr$(a);
     80   end if
     90 next
    

    I understand that the shell process one layer of escaped \ characters for each level of abstraction, and that a command or binary (I also tested /usr/bin/printf) parses another layer of escaped \ characters.

    But I don't understand why I could not get any form of either format to work in a shell script, which should work simply by adding an extra \ character for every one present in an output string.

    Does anyone know what is going on, or is this an actual bug?

    I believe this is a bug in BASH when in SH mode; see post below, or just save yourself the hassle and use the BAS script.

  • Paul Wratt
    Paul Wratt over 6 years
    I just tested the "portably" (double printf) which worked. So basically I just missed the right combinations (specifically $( \\ and %o )
  • Paul Wratt
    Paul Wratt over 6 years
    I want to keep maximum protability with low overhead, least installed, ie. not AWK and not PERL
  • Paul Wratt
    Paul Wratt over 6 years
    with the \x form, when it should have worked in a script it was getting interfered with producing xFFxFFxFFx00