printf: Build Strings and Bytes Reliably in CI
printf writes formatted output with no implicit trailing newline, unlike echo, so bytes are exact.
echo behaves differently across shells and adds a newline you may not want. printf is the portable way to emit exact strings, escapes, and raw bytes in CI.
What it does
printf applies a format string to its arguments, C-style. %s inserts a string, \n/\t are literal escapes, \xHH emits a raw byte, and the format is reused if there are extra arguments. Unlike echo it adds no trailing newline unless you write \n.
Common usage
# exact bytes, no trailing newline (stable for hashing)
printf '%s' "$CONTENT" | sha256sum
# raw bytes for a fixture (gzip magic)
printf '\x1f\x8b\x08\x00' > magic.head
# reuse the format across many args
printf '%s\n' "$@"
# build a key=value block
printf 'VERSION=%s\nSHA=%s\n' "$ver" "$sha" >> "$GITHUB_ENV"Options
| Directive | What it does |
|---|---|
| %s | Insert the argument as a string |
| %b | Insert the argument and interpret backslash escapes in it |
| %d / %x | Format the argument as decimal / hex integer |
| \n \t \r | Newline, tab, carriage return |
| \xHH | Emit the raw byte with hex value HH |
| \0NNN | Emit the raw byte with octal value NNN |
In CI
Use printf %s "$x" instead of echo "$x" whenever the exact bytes matter, e.g. before piping into sha256sum or a signature step: echo may append a newline that changes the hash. To append to $GITHUB_ENV or $GITHUB_OUTPUT, printf with explicit \n gives predictable line endings.
Common errors in CI
"printf: %s: expected a numeric value" happens when you use %d/%x on a non-numeric argument. A literal % in output must be written %%, or printf consumes it as a directive. If a value begins with - (e.g. -n), use printf %s -- "$val" or the value may be parsed as a flag. %b (not %s) is needed to expand \n embedded inside an argument.