Skip to content
Lesson 5 of 14

Step 1 of 5 · Reading · ~3 min

Learn

Strings and Output

printf and Heredocs

By the end of this lesson you can print aligned, formatted lines with printf and emit a multi-line block of text from a script with a heredoc. Between them they cover nearly every "produce exactly this text" job.

Where echo runs out

echo is fine until it is not. Its handling of flags and escapes (-n, -e) differs between shells and systems, and it cannot format: no padding, no decimal places, no zero fill. Scripts that matter use printf.

printf: a format string plus arguments

printf 'Player %s scored %d points\n' "Mia" 42

The first argument is the format. Every % sequence is a slot that consumes one of the following arguments: %s a string, %d an integer, %f a decimal number, and %% a literal percent sign. Three bash-specific facts:

  • No automatic newline. Every line must end with an explicit \n in the format. That also makes printf the clean way to print without a newline.
  • The values are arguments, not part of the format. Quote them ("$name") and keep them out of the format string.
  • The format is reused until the arguments run out. This is useful: printf '%s\n' red green blue prints three lines in one command.

Width, alignment and precision go between the % and the letter:

printf '[%6s]\n'   "ab"      # [    ab]   right-aligned in 6 columns
printf '[%-6s]\n'  "ab"      # [ab    ]   left-aligned
printf '[%04d]\n'  42        # [0042]     zero-padded integer
printf '[%.1f]\n'  2.71828   # [2.7]      one decimal place
printf '[%8.3f]\n' 2.71828   # [   2.718] width 8, three decimals

Width is a minimum. A longer value is printed in full and pushes the columns out of line, so choose widths from the longest value you expect. Also note that %d rejects non-integers: printf '%d' 2.5 is an error, while %f happily accepts the integer 2.

Reuse of the format gives you a small table in one call:

printf '%-8s %3d\n' "ada" 36 "grace" 85
ada       36
grace     85

Heredocs: a block of text as stdin

When a script has to emit a block (usage text, a config file, a report skeleton), a heredoc feeds literal lines to a command's stdin:

cat <<EOF
Backup report for $host
Run by $USER
EOF

Everything up to a line containing exactly EOF is the input. The delimiter's name is convention, not a keyword. Variables and $( ) expand inside the block, so the lines above print the real host name. Two variations:

  • <<'EOF' (quoted delimiter) turns expansion off, so $HOME stays a literal $HOME. Use it for text that contains its own $ signs, such as a script you are generating.
  • <<-EOF removes leading tab characters, so a block can be indented along with your code.

A here-string, <<<, is the one-line cousin: grep -q error <<< "$line" feeds a single string as stdin with no pipe and no extra process.

Common mistakes

  • No \n. Without it the next output continues on the same line, and the last line is never terminated.
  • Variables inside the format. printf "Name: $name\n" breaks when the value contains a %. Write printf 'Name: %s\n' "$name".
  • %d with a decimal. A price such as 3.50 needs %f or %s.
  • Spaces after the closing delimiter. The terminating line must contain only the delimiter, with no leading or trailing spaces, or the heredoc runs to the end of the file.

Your exercise

Print a small receipt. The header is a fixed block of text that includes one value, which is a natural fit for a heredoc. The item rows are aligned columns, which is a job for printf; since three rows go through the same format, one call can print all of them. The column widths are in the expected output, so count them there rather than guessing.

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…