Text processing · bash builtin
printf
Print text and values through a format string, with precise control over spacing, numbers and escapes.
printf FORMAT [ARGUMENT...]
printf prints its arguments according to a format string, the same idea as C's printf:
ordinary characters are printed as they are, %s takes the next argument as a string, %d
as an integer, %f as a decimal number, and \n, \t are escapes. It never adds a newline
you did not ask for.
It is the reliable way to print in scripts. Unlike echo, its behavior does not change
between shells or depending on what the data looks like: an argument of -n or one containing
backslashes prints literally through %s. When there are more arguments than format
specifiers, the format is reused until the arguments run out, which makes one-line loops
like printf '%s\n' "${files[@]}" possible.
In bash printf is a builtin with two extras: -v var stores the result in a variable
instead of printing it, and %q quotes a string so it can be pasted back into a shell. There
is also a standalone /usr/bin/printf from coreutils, used by sh, find -exec and xargs.
Examples
Print a value inside a sentence
printf 'Hello, %s. You have %d new messages.\n' "$USER" 3
Hello, dev. You have 3 new messages.
%s takes the first argument as a string and %d the second as an integer. The trailing
\n is required: printf adds no newline on its own.
Print without a trailing newline
printf 'Working...'; printf ' done\n'
Working... done
Both calls end up on one line because the first has no \n. This is the portable way to
write a prompt or a progress message, where echo -n is not portable.
Reuse the format for every argument
printf '%s\n' apple 'cherry pie' banana
apple cherry pie banana
There is one %s and three arguments, so the format is applied three times. Quoting keeps
cherry pie as one argument. printf '%s\n' "${array[@]}" prints an array one element per
line the same way.
Consume arguments in pairs
printf '%-8s -> %s\n' $(cat data/hosts.txt)
web01 -> 10.0.0.11 web02 -> 10.0.0.12 db01 -> 10.0.0.21 cache01 -> 10.0.0.31
Each pass through the format uses two arguments. The unquoted $(cat ...) splits the file
into words, so the four lines become eight arguments and four output lines. That
word splitting is fine here only because no field contains spaces.
Align columns with widths
printf '%-10s|%6s|%-6s|\n' name qty price
printf '%-10s|%6d|%6.2f|\n' widget 12 4.5 gadget 3 19.99
name | qty|price | widget | 12| 4.50| gadget | 3| 19.99|
A number after % sets the minimum width, and - left-aligns. %6.2f is six characters
wide with two decimals. Values are padded, never truncated, so a long value pushes the
columns out of line.
Format numbers
printf '%05d | %.3f | %x | %o | %e | %+d\n' 42 3.14159 255 8 123456 7
00042 | 3.142 | ff | 10 | 1.234560e+05 | +7
%05d pads with zeros, %.3f rounds to three decimals, %x is hexadecimal, %o octal,
%e scientific notation and %+d always shows a sign.
Print a literal percent sign
printf 'disk usage: %d%%\n' 91
disk usage: 91%
%% prints one %. A lone % followed by a letter would be taken as a format specifier.
Never put data in the format string
msg='Disk 100% done'
printf "$msg\n"
printf '%s\n' "$msg"
Disk 100 0one Disk 100% done
In the first call the message is the format, and % d in it is a valid specifier (a
space flag plus d). With no argument left it prints 0, eating the d of done. Pass
data as an argument to %s, as the second call does, and it prints exactly.
Expand escapes inside an argument with %b
line='name\tqty'
printf '%s\n' "$line"
printf '%b\n' "$line"
name\tqty name qty
Escapes in the format are always interpreted, but %s prints its argument literally,
backslashes included. %b interprets escapes such as \t in the argument, which is how to
print a string that deliberately contains them.
Quote a string for reuse in the shell with %q
printf '%q\n' 'file with spaces.txt' "it's" $'tab\there'
file\ with\ spaces.txt it\'s $'tab\there'
%q (bash and GNU coreutils) escapes a string so that the shell would read it back as the
same single word. Useful for logging the exact command a script is about to run, or for
building a command to pass to ssh. Strings with control characters come out in $'...'
form.
%q is not available in sh
printf '%q\n' 'a b'
/bin/sh: 1: printf: %q: invalid directive
Under dash, the /bin/sh on Debian and Ubuntu, the builtin printf does not know %q
and fails with status 2. In POSIX scripts quote by hand, or call /usr/bin/printf, whose
GNU version supports %q.
Store the result in a variable with -v
printf -v stamp '%s-%03d' build 7
echo "artifact: $stamp.tar.gz"
artifact: build-007.tar.gz
In bash, -v name assigns the formatted string to a variable instead of printing it. It
avoids a $(...) subshell and keeps trailing newlines, which command substitution strips.
Print the current date without calling date
printf 'today is %(%Y-%m-%d)T, epoch %(%s)T\n' -1 -1
today is 2026-10-31, epoch 1793478032
bash's %(fmt)T formats a timestamp with strftime codes; -1 means now. It saves
starting a date process in loops. This is bash-only.
Repeat a character
printf '%.0s-' {1..30}; printf '\n'
------------------------------
Brace expansion produces 30 arguments; %.0s prints each with zero characters of
precision, so only the - in the format appears, once per argument. A quick way to draw a
separator line.
A non-numeric argument to %d is an error
printf '%d\n' 12abc
/bin/bash: line 1: printf: 12abc: invalid number 12
printf prints what it could parse (12), complains on stderr and returns status 1, so
scripts can catch bad input. An empty argument for %d quietly prints 0.
Common pitfalls
printf "$var"treats the data as a format: any%or\in it is interpreted. Always writeprintf '%s' "$var".printf adds no newline. A missing
\nleaves the shell prompt glued to the output.Too few arguments are filled with empty strings or zeros, without any warning, so a mistyped format can print plausible-looking garbage.
%ddoes not accept decimals:printf '%d' 3.7prints3, complains, and returns 1. Use%.0fto round, and note it rounds halves to even (2.5gives2).
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
%qand-vare bash builtin features;%(...)Tis bash-only. The standalone GNUprintfalso has%q; the macOS/usr/bin/printfdoes not.The format escapes and
%s %d %f %x %o %b %care POSIX and work in dash, bash, zsh and BSD printf alike.
Options worth knowing
A selection, described in our own words. For the full list, see printf(1).
| Option | What it does |
|---|---|
-v VAR | Assign the output to shell variable VAR instead of printing (bash builtin). |
Related
echoPrint its arguments separated by spaces, followed by a newline.awkScan text as records and fields, and run a small program on each line that matches a pattern.seqPrint a sequence of numbers, with a chosen start, step, separator and format.- QuotingSingle quotes, double quotes, backslashes and $'...', what each protects, and the unquoted-variable bugs they prevent, each shown failing and then fixed.
- POSIX sh vs bashThe bashisms that break when a script runs under dash or another POSIX sh, the exact errors dash prints, and the portable replacement for each.
- String manipulationLength, substrings, prefix and suffix removal, search and replace, case, trimming, splitting and joining, done with bash's own expansions instead of external tools.