Skip to content
Lesson 5 of 14

Step 1 of 5 · Reading · ~3 min

Learn

Strings and Output

printf and Heredocs

echo got you this far, and now it's time to meet its limits — and the two tools that replace it when output gets serious: printf for formatted lines, heredocs for blocks. Between them, they cover every "produce exactly this text" job a script will ever have.

echo's dirty secret

echo is fine until it isn't: its behavior with flags and escapes differs between systems and shells (echo -n, echo -e — sometimes flags, sometimes printed literally; a portability mess with decades of history). And it can't format: no padding, no decimal control, no zero-fill. The professional default in scripts that matter is:

printf: C's workhorse, shell edition

printf "Hi, %s. You're %d.\n" "$name" "$age"

Same format-string idea as C/Go (this platform teaches it thrice — it's that foundational): %s string, %d integer, %% a literal percent. Two bash-specific facts to pin:

  • No automatic newline. printf "x" leaves the cursor hanging; every line you want ends in an explicit \n. (This makes printf the clean way to print without a newline, too — no echo -n portability roulette.)
  • The format recycles. Extra arguments re-run the format: printf "%s\n" a b c prints three lines. That's a genuinely useful idiom — one-per-line output of a list in a single command.

Width and precision work as in C:

printf "%5d|\n" 42        #    42|     — right-aligned, width 5
printf "%-5d|\n" 42       # 42   |     — left-aligned
printf "%05d\n" 42        # 00042      — zero-padded
printf "%.2f\n" 3.14159   # 3.14       — yes, printf CAN format decimals…

…which deserves a callout given last chapter's news: bash arithmetic can't produce floats, but printf can format float-shaped text it's handed — the missing piece when bc/awk output needs tidying.

Heredocs: multi-line output without a hundred echos

When a script needs to emit a block — usage text, a config file, a report template — the heredoc feeds literal lines as stdin:

cat << EOF
Usage: $0 [options] <file>

Processes <file> and reports totals.
Run on $(date).
EOF

<< EOF says "everything until a line containing exactly EOF is input" (the delimiter's name is convention, not keyword). Variables and command substitutions expand inside — the block above interpolates the script name and date. Two variations complete the set:

  • << 'EOF' (quoted delimiter) — expansion OFF; every $ is literal. For emitting code or configs that contain their own $variables.
  • <<- EOF — strips leading tabs, letting the block indent with your code.

Heredocs are everywhere in real operations: embedded SQL for psql, YAML for kubectl apply -f -, generated systemd units. The pattern is always the same — a program that reads stdin, fed a template from the script itself.

The one-line cousin: here-strings

When the input is a single string rather than a block, <<< feeds it straight in:

grep -q "error" <<< "$line"   # no pipe, no extra process

A here-string is a one-line heredoc, and it replaces the wasteful echo "$line" | grep ... idiom. Same family, three members: << a block, << 'EOF' a block with expansion off, <<< a single string.

Your exercise: Format Output

Produce a line with printf instead of echo. The method that works every time: write the target output down, then build the format string left to right, one % spec per value, checking each against the sample. If columns have to line up, count the width in the expected output and encode it (%-10s, %6d) rather than eyeballing spaces.

Two things the submission log says people forget. printf adds no newline, so a format that does not end in a newline escape produces a line the grader never sees terminated. And the values are arguments, not text inside the format -- a format string with the variable names spelled out in it prints those names.

Up nextif and caseControl Flow

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…