Skip to content
VLSI Mentor

awk Part II · Module 7 · Input, Output, and Multi-File Processing

print and printf

Module 6 produced numbers. Module 7 is about getting them out in a shape something else can use — a human reading a terminal, a spreadsheet, a dashboard, or the next stage of a pipeline. It starts with the two statements that do the writing. On simple output they are interchangeable, which is why most people pick one by habit and are then surprised twice: print quietly inserts your output separators and printf ignores them completely, and their argument-count rules are exact opposites — too few arguments kills the run, too many are discarded without a word.

Foundation15 min readawkPart IIOutputprintfFormatting

Part II · Module 7 · Chapter 7.1 · print and printf

1. Why This Matters

Everything in Modules 5 and 6 ended with output, and every command used whichever statement happened to fit. That was deliberate — the distinction only matters once the output has to be consumed by something, which is this module's subject.

The short version: print is for getting data out, printf is for getting it out in a specific shape. A terminal check wants print. A table that must stay aligned, a CSV a spreadsheet will load, or a number that must show exactly two decimals wants printf.

The reason the chapter is longer than that sentence is that the two differ in four ways, and two of them are silent.

2. print

print writes its arguments, separated by OFS, followed by ORS:

Azvya Education Pvt. Ltd.VLSI Mentor
cov.txt — synthetic teaching data. A block, a score, and a runtime.
alu 95 1.5
fifo 72 12.25
uart 88 0.5
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ print $1, $2 }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu 95
fifo 72
uart 88

The commas are not punctuation inside a function call — each one is a place where OFS goes. That is Chapter 5.2 section 8's point and it becomes important in section 5.

print with no arguments writes $0:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR == 1 { print }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu 95 1.5

Which is the shortest way to pass a record through, and the form Chapter 4.1 established is implied when a pattern has no action at all.

3. printf

printf takes a format string and fills in the conversions. Nothing is inserted that you did not write:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%s %s\n", $1, $2 }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu 95
fifo 72
uart 88

Identical output to section 2 — because the format string contains a literal space and a literal \n, which is what print would have supplied from OFS and ORS.

Leave the \n out and there is no newline anywhere:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%s ", $1 }' cov.txt; echo "[END]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu fifo uart [END]

The [END] marker lands on the same line as the output, which is the proof that no newline was emitted. Without the marker, the shell prompt would appear there instead.

That is not a defect; it is the point. printf emits exactly what you asked for, which is what makes it usable for building a line in pieces.

4. The Four Differences

printprintf
separator between itemsinserts OFSnothing — you write it
line terminatorappends ORSnothing — you write \n
too few argumentsn/afatal error
too many argumentsprints them allsilently discarded
format reusen/adoes not cycle

Sections 5 to 7 demonstrate each.

5. print Respects OFS and ORS; printf Ignores Both

This is the difference people hit first. Set a separator and use both statements:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { OFS = "|" } { print $1, $2 }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu|95
fifo|72
uart|88
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { OFS = "|" } { printf "%s %s\n", $1, $2 }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu 95
fifo 72
uart 88

OFS had no effect on the printf. The space between the two values is the literal space in the format string.

ORS behaves the same way:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { ORS = " ;" } { print $1 }' cov.txt; echo "[END]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu ;fifo ;uart ;[END]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { ORS = " ;" } { printf "%s\n", $1 }' cov.txt; echo "[END]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu
fifo
uart
[END]

The print version ends every record with ; — including the last, which is Chapter 5.2 section 8's trailing-separator problem. The printf version ignores ORS entirely and the \n in the format decides the line endings.

6. Argument Count: Fatal One Way, Silent the Other

Too few arguments for the conversions is a fatal error:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { printf "%s|%s|%s\n", "a" }' 2>&1
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk: not enough args in printf(%s|%s|%s
)
 source line number 1

The run stops. (The message prints the format string including its newline, which is why it wraps oddly.)

Too many arguments is silent:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { printf "%s\n", "a", "b", "c" }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a

b and c are gone. No warning on stderr, no non-zero status, no indication that two values were dropped.

7. The Format Does Not Cycle

Anyone arriving from shell scripting expects a format to repeat over extra arguments, because the shell's printf does:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
printf '%s-' a b c; echo
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a-b-c-

awk's does not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { printf "%s-", "a", "b", "c"; print "" }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a-

One pass through the format, extras discarded per section 6.

So where a shell script loops a format over a list, awk needs an explicit loop:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { split("a b c", v, " "); for (i = 1; i <= 3; i++) printf "%s-", v[i]; print "" }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a-b-c-

That is Chapter 5.5's for doing the repetition the format will not.

8. Which to Use

print when you are dumping data, passing a record through, or want the separator to stay configurable. It is shorter and it is the idiomatic choice for a one-liner.

printf when the output has a shape something depends on — an aligned table, a fixed number of decimals, a delimited file, or a line assembled from several statements.

Two practical notes.

A printf with no conversions is a fine way to write a fixed string with no ORS surprises:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'END { printf "done\n" }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
done

And printf is how you build one line from many records, since it does not terminate anything:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%s%s", sep, $1; sep = ", " } END { printf "\n" }' cov.txt | cat -e
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu, fifo, uart$

The primed-separator idiom from Chapter 5.2 — with printf rather than print, because print would append ORS after every record and there is no value of ORS that omits the last one.

9. Common Mistakes

Setting OFS and using printf. printf ignores it — section 5.

Forgetting \n in a printf. No newline is emitted at all.

Writing printf with too many arguments. Silently discarded, so a column disappears — section 6.

Expecting the format to cycle. It does not; awk needs an explicit loop — section 7.

Expecting print a, b to be a function call. The commas are OFS positions, not argument separators.

Using print for a delimited file. It works until someone changes OFS; printf makes the format local.

Assuming print with no arguments prints nothing. It prints $0.

10. Exercises

Exercise 1 — Predict the output

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { OFS = "-" } BEGIN { print "a", "b"; printf "%s %s\n", "a", "b" }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a-b
a b

print used OFS; printf used the literal space in its format.

Exercise 2 — Convert between the two

Rewrite this print as an equivalent printf, keeping the output identical.

Azvya Education Pvt. Ltd.VLSI Mentor
cov.txt
alu 95 1.5
fifo 72 12.25
uart 88 0.5
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ print $1, $3 }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu 1.5
fifo 12.25
uart 0.5
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%s %s\n", $1, $3 }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu 1.5
fifo 12.25
uart 0.5

The printf spells out what print took from OFS and ORS.

Exercise 3 — Debug a disappearing column

A report is supposed to show three columns and shows two.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%s %s\n", $1, $2, $3 }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu 95
fifo 72
uart 88

Three arguments, two conversions. The third is silently discarded — section 6. Nothing on stderr and the exit status is 0.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%s %s %s\n", $1, $2, $3 }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu 95 1.5
fifo 72 12.25
uart 88 0.5

Compare what happens when the error goes the other way:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%s %s %s %s\n", $1, $2, $3 }' cov.txt 2>&1 | head -3
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk: not enough args in printf(%s %s %s %s
)
 input record number 1, file cov.txt

Note the message names the offending record and file, which a BEGIN-only failure cannot — there it says source line number 1 instead.

Fatal and immediate. One direction stops you; the other lets you ship.

Exercise 4 — ORS against printf

Predict both, then explain why only one is affected.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { ORS = "|" } { print $1 }' cov.txt; echo "[END]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu|fifo|uart|[END]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { ORS = "|" } { printf "%s|", $1 }' cov.txt; echo "[END]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu|fifo|uart|[END]

Identical output from different mechanisms — the first from ORS, the second from a literal | in the format. Changing ORS would alter the first and not the second, which is section 5's configurable-at-a-distance point.

Note both end with a trailing |. The print version cannot avoid it; the printf version can, with the primed separator from section 8.

Exercise 5 — Build one line from many records

Produce a single comma-separated line of block names with no trailing comma and a terminating newline.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%s%s", sep, $1; sep = ", " } END { printf "\n" }' cov.txt; echo "[END]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu, fifo, uart
[END]

[END] is on its own line this time, because the END rule supplied the newline. sep is unset on the first record so nothing precedes alu, and is ", " thereafter — a separator between items rather than after each. print cannot do this, because ORS is appended unconditionally.

Exercise 6 — Engineering application

Emit a per-block line for a report, with the block name left-aligned in a fixed column and the runtime to two decimals — then the same data as a passthrough dump for a colleague who wants the raw rows.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%-8s %6.2f s\n", $1, $3 }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu        1.50 s
fifo      12.25 s
uart       0.50 s
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ print }' cov.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu 95 1.5
fifo 72 12.25
uart 88 0.5

The first is printf's job — the column widths and the two decimals are part of what makes the output readable, and they must not change because someone set OFS. The second is print's — no shape is being imposed, so the shorter statement is the right one.

Note 12.25 s aligns under 1.50 s because %6.2f pads to a fixed width. Chapter 7.2 is about that padding, and about what happens when a value is wider than the field you gave it.

Exercise 7 — Challenge: a format-and-argument count check

A long printf is easy to get wrong. Write a command that reports how many conversions a format string contains, so the count can be checked against the arguments.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { f = "%-8s %6.2f %d %s\n"; n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", f); printf "the format has %d conversion(s)\n", n }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
the format has 4 conversion(s)

gsub returns a count of matches (Module 8's function, used here only to count). The pattern matches a %, any flags, width or precision, then a conversion letter.

It is not trustworthy yet, and finding out why is the exercise. Try it on a format containing a literal percent sign:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { f = "%d%% of %d bins\n"; n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", f); printf "naive count: %d (should be 2)\n", n }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
naive count: 3 (should be 2)

Three. The format has two real conversions, %d and %d.

The extra match is % o — from %% of. The flag character class [-+ #0-9.] contains a space, so the pattern read the second % of %%, took the following space as a flag, and accepted the o of of as a conversion letter. A perfectly reasonable-looking pattern matched four characters of English prose.

It fails the other way too, on a format where a conversion follows the escape:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { f = "%d%%s\n"; n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", f); printf "naive count: %d (should be 1)\n", n }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
naive count: 2 (should be 1)

Two, where %d%%s has exactly one conversion — the %% consumed a % and the trailing s was read as a second.

Removing the escaped percents before counting fixes both:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { f = "%d%%s\n"; g = f; gsub(/%%/, "", g); n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", g); printf "corrected count: %d (should be 1)\n", n }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
corrected count: 1 (should be 1)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { f = "%d%% of %d bins\n"; g = f; gsub(/%%/, "", g); n = gsub(/%[-+ #0-9.]*[a-zA-Z]/, "&", g); printf "corrected count: %d (should be 2)\n", n }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
corrected count: 2 (should be 2)

Both right, and note g = f first so the original format is not modified — gsub edits in place.

Two lessons, and the second is the one worth keeping. A character class you did not read carefully will match things you did not imagine — the space in [-+ #0-9.] is legitimate, since % d is a real conversion with a space flag, and it is exactly what let the pattern eat prose. And a checker built from the same pattern class as the thing it checks shares its blind spots: the repair works because stripping %% is a different operation from counting conversions, not more of the same pattern. That is Chapter 5.3's positive-guard argument in a new setting.

11. Summary

  • print inserts OFS between items and ORS at the end. The commas are separator positions, not argument separators.
  • printf inserts nothing. You get exactly the format string, including no newline unless you write \n.
  • printf ignores OFS and ORS completely, which makes its output local and unconfigurable — the reason to use it for a format something depends on.
  • Too few printf arguments is a fatal error.
  • Too many are silently discarded, so an edited format can drop a column with no warning.
  • That asymmetry is the inverse of the function-argument rule in Chapter 6.5, where too many warns and too few is silent.
  • The format does not cycle over extra arguments the way the shell's printf does. Use an explicit loop.
  • print with no arguments prints $0.
  • Use print for data and a configurable separator; printf when the shape is part of the contract.

Both statements are now distinguishable. What printf does with its conversions — widths, alignment, decimal places, and what happens when a value does not fit the field you gave it — is Chapter 7.2 — Output Formatting, which also covers the rounding rule that turns 2.5 into 2.

Where this fits

Part of the awk curriculum.