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
\nin the format. That also makesprintfthe 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 blueprints 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$HOMEstays a literal$HOME. Use it for text that contains its own$signs, such as a script you are generating.<<-EOFremoves 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%. Writeprintf 'Name: %s\n' "$name". %dwith a decimal. A price such as3.50needs%for%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.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…