------------------------------------------------------------------------------- Printf in scripting languages ------------------------------------------------------------------------------- Type Checking The C programming library (from which printf() derived), is very strict on the type for the arguments given to the function (any function actually). The Bash built-in or external command printf also has this strict type checking, reporting an error if it is not the case. Which is strange considering all arguments are in reality, just strings. printf "%d\n" 3.9 -bash: printf: 3.9: invalid number 3 /bin/printf "%d\n" 3.9 /bin/printf: ‘3.9’: value not completely converted 3 Perl and awk (which is typed), will silently truncate the number to become an integer, as you would expect. perl -e 'printf "%d\n", 3.9;' 3 awk 'END{printf "%d\n", 3.9;}' /dev/null 3 ------------------------------------------------------------------------------- Printf and UTF-8 strings The POSIX requirements specify that the C printf() is to do field calculations in bytes for backward compatibity. As sucn many "printf" function will display field shorter than expected if the string contains UTF-8 characters just as it would if the string contains control codes for color or bold. The library UTF-8 printf() is wprintf() See Why is printf “shrinking” umlaut? https://unix.stackexchange.com/questions/350240 printf: multibyte characters https://unix.stackexchange.com/questions/405113/ UTF-8 Width Display Issue of Chinese Characters (posible solutions) https://stackoverflow.com/questions/10751867/ Here is a list of what understands UTF-8 string lengths for field size. POSIX complient (not UTF-8 compatible) Output field is only 14 characters, rather than the requested 30! EG the result is only: |✓✔√⍻✗✘✕✖ | Bash printf "|%-30s|\n" "✓✔√⍻✗✘✕✖" GNU Command /bin/printf "|%-30s|\n" "✓✔√⍻✗✘✕✖" perl direct perl -e 'printf "|%-30s|\n", "✓✔√⍻✗✘✕✖"' UTF-8 compatible... Result is: |✓✔√⍻✗✘✕✖ | awk awk 'END{printf "|%-30s|\n", "✓✔√⍻✗✘✕✖";}' /dev/null perl done correctly perl -CO -Mutf8 -e 'printf "|%-30s|\n", "✓✔√⍻✗✘✕✖"' python formated string python -c 'print("|%-30s|" % "✓✔√⍻✗✘✕✖" )' multiple args python -c 'print("|%*s|" % (-30,"✓✔√⍻✗✘✕✖") )' The only good solution is to do the display width to field width calculations yourself, especially as passing random strings to other programs (like python) is not as simple as you may think. BASH at least gives character length in its ${#var} output. Other things to watch out for or avoid if posible... * Newlines, returns, tabs, and other control codes * Color Escapes * TTY escape codes (moving cursor around) * TTY Title Bar Set Codes * Double-Wide Characters (Chinese) * Zero-width characters * Emoji * Proportional fonts on display output! (game-over for TTY's) BASH avoids many of this in it prompt handled by requireing the user to identify such control sequences. ------------------------------------------------------------------------------- Number Formatting (printf) WARNING: Many programs (bash, perl, awk, printf) treat a number with a leading '0' as being Octal. But what if it isn't a Octal, but Decimal! This can depend if the argument is being treated as a string or as number. printf "%d\n" 013 # bash builtin - BAD 11 /bin/printf "%d\n" 013 # external cmd - BAD 11 perl -e 'printf "%d\n", 013;' # BAD treated as octal 11 perl -e 'printf "%d\n", "013";' # GOOD if input is a string 13 awk 'END{printf "%d\n", 013;}' /dev/null # BAD treated as octal 11 awk 'END{printf "%d\n", "013";}' /dev/null # GOOD if input is a string 13 To fix in BASH use the expression $((10#$num)), which will ensure the number is treated as decimal, and removes any leading zeros from the number, before giving it to the printf command (built-in or otherwise). echo $((10#013)) 13 printf "%d\n" $((10#013)) 13 ------------------------------------------------------------------------------- Number Decimal Places... Using %.2g will take a floating point number and only output 2 significant digits but keep the result as short as posible! printf "%.2g\n" 3.38 # -> "3.4" printf "%.2g\n" 4.01 # -> "4" contrast this with the more normal "%.2f" formating... printf "%.2f\n" 3.377 # -> "3.38" printf "%.2f\n" 4 # -> "4.00" ------------------------------------------------------------------------------- BASH Printf Date functions BASH printf has a '%(date_format)T' formatting, taking an argument of a date in seconds. If argument is -1 use the time NOW, -2 time when BASH started As it is a builtin, the output can be saved directly into a BASH variable! # Save current time in seconds into variable! printf -v now '%(%s)T' -1 # print saved time in IEEE date format printf '%(%F_%T)T' $now Example... Progressive Time out waiting for condition.... With fancy rotating line... # Initialization printf -v start '%(%s)T' -1 # start time heartbeat='-\|/'; beatlen=4 timeout=30 # Wait loop while true; do # wait for a server to appear -- adjust to suit # http_code=$(curl -s -o/dev/null -w'%{http_code}' https://localhost:8443/) # case "$http_code" in # 501|504) : ;; # not started yet # 302) break ;; # SUCCESS - break loop # *) echo "Unknown HTTP response $http_code"; break ;; # esac printf -v elapsed '%(%s)T' -1 # elapsed time if (( elapsed - start > timeout )); then printf "FAILED" break #exit 10 fi beat=$(( (beat += 1)%beatlen )) printf "%c Waiting... %d\r" "${heartbeat:$beat:1}" $(( elapsed - start )) sleep .1 # max speed of rotating line done printf " \n" -------------------------------------------------------------------------------