🖨️ printf — Formatted Output

What is printf?

The printf builtin formats and prints data with precise control over spacing, alignment, padding, and numeric representation. It is the go-to tool whenever echo isn't enough — tabular output, fixed-width fields, zero-padded numbers, hex conversion, and writing to variables rather than stdout.

Unlike echo, printf does not add a newline automatically, does not interpret escape sequences by default in arguments, and behaves consistently across all platforms. For serious scripting, printf is nearly always the better choice.

Syntax:
printf FORMAT [ARGUMENT ...]

printf "%s\n" "string"          # print string with newline
printf "%-10s %5d\n" "label" 42  # left-align, right-align
printf -v VAR "FORMAT" ARGS      # write to variable, not stdout

Format Specifiers

SpecifierTypeExampleOutput
%sStringprintf "%s" "hello"hello
%dDecimal integerprintf "%d" 4242
%iInteger (same as %d)printf "%i" 4242
%fFloating pointprintf "%f" 3.143.140000
%eScientific notationprintf "%e" 123451.234500e+04
%oOctalprintf "%o" 810
%xHexadecimal (lower)printf "%x" 255ff
%XHexadecimal (upper)printf "%X" 255FF
%cSingle characterprintf "%c" 65A
%bString with backslash escapesprintf "%b" "a\tb"a   b
%%Literal percent signprintf "100%%"100%

Width and Precision Modifiers

ModifierMeaningExampleOutput
%10sRight-align in 10-char fieldprintf "%10s" "hi" hi
%-10sLeft-align in 10-char fieldprintf "%-10s" "hi"hi
%05dZero-pad to 5 digitsprintf "%05d" 4200042
%+dAlways show signprintf "%+d" 42+42
%.2f2 decimal placesprintf "%.2f" 3.141593.14
%8.2fWidth 8, 2 decimal placesprintf "%8.2f" 3.14159 3.14
%*sWidth from argumentprintf "%*s" 10 "hi" hi

Examples

1
Basic String and Number Output

The fundamentals — printf vs echo:

# echo adds newline automatically; printf does not
echo "Hello World"
printf "Hello World\n"     # equivalent

# Multiple arguments — format string repeats
printf "%s\n" "alpha" "beta" "gamma"

# String and integer together
printf "Name: %s  Age: %d\n" "Craig" 42

# echo -e vs printf %b for escape sequences
printf "Column1\tColumn2\tColumn3\n"
printf "%b" "Line one\nLine two\nLine three\n"
Output:
Hello World alpha beta gamma Name: Craig Age: 42 Column1 Column2 Column3 Line one Line two Line three
💡 Format repeats: When you supply more arguments than the format has specifiers, printf repeats the format string from the beginning. printf "%s\n" a b c prints each on its own line — very useful for processing lists.
2
Aligned Tabular Output

The most common sysadmin use — clean aligned columns:

#!/bin/bash
# Print a service status table

printf "%-15s %-10s %8s\n" "SERVICE" "STATUS" "PID"
printf "%-15s %-10s %8s\n" "-------" "------" "---"

services=("nginx" "sshd" "postgresql" "redis" "crond")
for svc in "${services[@]}"; do
    if systemctl is-active --quiet "$svc" 2>/dev/null; then
        pid=$(systemctl show -p MainPID --value "$svc")
        printf "%-15s %-10s %8s\n" "$svc" "running" "$pid"
    else
        printf "%-15s %-10s %8s\n" "$svc" "stopped" "-"
    fi
done
Output:
SERVICE STATUS PID ------- ------ --- nginx running 1234 sshd running 891 postgresql running 2201 redis stopped - crond running 445
💡 %-15s means: left-align (-), in a field 15 characters wide (15), string type (s). Right-align by omitting the minus. Consistent widths across header and data rows make the column snap into alignment.
3
Number Formatting — Integers and Floats
# Zero-padded numbers — useful for filenames, sequences
for i in {1..5}; do
    printf "backup_%04d.tar.gz\n" "$i"
done

# Floating point precision
printf "Pi: %.4f\n" 3.14159265
printf "Price: $%8.2f\n" 1234.5

# Always show sign
printf "Temp delta: %+.1f C\n" -2.7
printf "Temp delta: %+.1f C\n" 4.3

# Hex and octal conversion
printf "Dec: %d  Hex: %#x  Oct: %#o\n" 255 255 255

# Scientific notation
printf "%.3e\n" 0.000123456
Output:
backup_0001.tar.gz backup_0002.tar.gz backup_0003.tar.gz backup_0004.tar.gz backup_0005.tar.gz Pi: 3.1416 Price: $ 1234.50 Temp delta: -2.7 C Temp delta: +4.3 C Dec: 255 Hex: 0xff Oct: 0377 1.235e-04
%# flag: The # flag adds the type prefix — 0x for hex, 0 for octal. Useful when the output needs to be unambiguous about its base.
4
Writing to a Variable with -v

The -v flag writes formatted output into a variable instead of stdout — no subshell needed:

# Store formatted string in a variable
printf -v timestamp "%s" "$(date '+%Y-%m-%d %H:%M:%S')"
printf -v logline "[%s] %s: %s\n" "$timestamp" "INFO" "Service started"
echo "$logline"

# Build a zero-padded filename
printf -v filename "report_%04d.html" "$report_num"
echo "Writing to: $filename"

# Format a table row for later use
printf -v row "%-20s %10.2f %8d\n" "$dept" "$budget" "$headcount"
report+="$row"    # accumulate into a report string
💡 -v avoids a subshell: var=$(printf ...) spawns a subshell. printf -v var ... does not — it is faster and does not strip trailing newlines the way command substitution does.
5
Escape Sequences
# Common escape sequences in format string
printf "Tab:\there\n"
printf "Newline:\nSecond line\n"
printf "Bell:\a\n"                  # terminal bell
printf "Carriage return:\r"         # overwrite current line
printf "Backslash: \\\n"
printf "No newline at end: "        # useful for prompts

# ANSI color codes with printf
printf "\033[32m%s\033[0m\n" "This is green"
printf "\033[31mERROR:\033[0m %s\n" "something failed"
printf "\033[33mWARN:\033[0m  %s\n" "disk at 85%%"
printf "\033[34mINFO:\033[0m  %s\n" "service started"

# Unicode characters
printf "\U2714 Done\n"     # ✔ Done
printf "\U274C Failed\n"   # ✖ Failed
Output (color codes render in terminal):
Tab: here Newline: Second line ✔ Done ✖ Failed
⚠️ %% for literal percent: Inside a printf format string, a bare % starts a specifier. To print a literal percent sign use %% — e.g., printf "Disk: %d%%\n" 85 prints Disk: 85%.
6
Generating Reports and Log Lines
#!/bin/bash
# Disk usage report with printf formatting

BORDER=$(printf '%0.s-' {1..55})

printf "\n%s\n" "$BORDER"
printf "  %-20s %10s %10s %8s\n" "FILESYSTEM" "SIZE" "USED" "USE%"
printf "%s\n" "$BORDER"

df -h --output=target,size,used,pcent | tail -n +2 | while read -r fs size used pct; do
    pct_num=${pct//%/}
    if (( pct_num >= 90 )); then
        flag="(!)"
    elif (( pct_num >= 75 )); then
        flag="(*)"
    else
        flag=""
    fi
    printf "  %-20s %10s %10s %7s %s\n" "$fs" "$size" "$used" "$pct" "$flag"
done

printf "%s\n\n" "$BORDER"
Output:
------------------------------------------------------- FILESYSTEM SIZE USED USE% ------------------------------------------------------- / 50G 22G 44% /boot 500M 120M 24% /var 30G 27G 90% (!) /home 100G 45G 45% -------------------------------------------------------
7
printf vs echo — When to Use Which
# echo is fine for simple output
echo "Starting backup..."
echo "Done."

# printf is better when:

# 1. Content might start with a dash (echo -n problem)
var="-n tricky"
echo $var          # echo interprets -n as a flag!
printf "%s\n" "$var"   # safe — always treated as data

# 2. You need precise field widths
printf "%-12s %s\n" "Hostname:" "$(hostname)"
printf "%-12s %s\n" "Uptime:"   "$(uptime -p)"
printf "%-12s %s\n" "Kernel:"   "$(uname -r)"

# 3. You need no trailing newline (for progress indicators)
for i in {1..10}; do
    printf "\rProgress: %3d%%" "$((i*10))"
    sleep 0.2
done
printf "\n"

# 4. Writing to a variable cleanly
printf -v greeting "Hello, %s! You have %d messages." "$USER" 5
Output:
Hostname: bambustudio Uptime: up 3 days, 4 hours Kernel: 5.15.0-91-generic Progress: 100%
8
Practical Sysadmin Patterns
# Timestamped log function
log() {
    local level=$1; shift
    printf "[%s] [%-5s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$level" "$*"
}

log INFO  "Starting deployment"
log WARN  "Config file missing — using defaults"
log ERROR "Cannot connect to database"

# Progress bar
progress_bar() {
    local current=$1 total=$2
    local pct=$(( current * 100 / total ))
    local filled=$(( pct / 2 ))
    local bar
    printf -v bar "%-50s" "$(printf '%0.s#' $(seq 1 $filled))"
    printf "\r[%s] %3d%%" "${bar// /-}" "$pct"
}

# Hex dump a string
string="Hello"
printf '%s' "$string" | od -A x -t x1z

# Generate separator lines of exact width
printf '%*s\n' 60 '' | tr ' ' '='    # 60 equal signs
printf '%0.s-' {1..40}; echo         # 40 dashes
Output:
[2026-04-08 14:22:01] [INFO ] Starting deployment [2026-04-08 14:22:01] [WARN ] Config file missing — using defaults [2026-04-08 14:22:01] [ERROR] Cannot connect to database ============================================================ ----------------------------------------

Quick Reference

PatternWhat it does
printf "%s\n" "$var"Safe string print — always treats content as data
printf "%-20s %10s\n"Left-align 20, right-align 10 — column layout
printf "%05d\n" "$n"Zero-pad integer to 5 digits
printf "%.2f\n" "$n"Float to 2 decimal places
printf "%x\n" "$n"Decimal to hexadecimal
printf "%o\n" "$n"Decimal to octal
printf "%%\n"Literal percent sign
printf -v VAR "fmt" argsWrite to variable — no subshell
printf "%s\n" a b cFormat repeats — prints each on own line
printf "\033[32m%s\033[0m\n"ANSI green color
printf '\r%s' "$msg"Overwrite current line (progress)
printf '%0.s-' {1..40}Repeat character N times

printf vs echo

SituationUse
Simple message, no formattingecho — simpler
Content might start with -printf "%s\n" — safe
Aligned columns / fixed widthprintf
Number formattingprintf
Write to variableprintf -v
No trailing newlineprintf (or echo -n)
ANSI color codesprintf — more reliable
Portable across sh/bash/dashprintf — consistent behavior