Skip to content
Latchkey

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

Terminal
# 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

DirectiveWhat it does
%sInsert the argument as a string
%bInsert the argument and interpret backslash escapes in it
%d / %xFormat the argument as decimal / hex integer
\n \t \rNewline, tab, carriage return
\xHHEmit the raw byte with hex value HH
\0NNNEmit 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.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →