4

I read another answer that describes how to use AWK to view the last line of output:

$ seq 42 | awk 'END { print }'
42

So it seems like when the END block is run the last line is loaded in $0.

This surprised me because the first line isn't loaded into the BEGIN block:

$ seq 42 | awk 'BEGIN { print }'
#=> blank
  • Is this behavior documentation anywhere? (I searched through the man page but didn't find anything)
0

2 Answers 2

9

The BEGIN block is run before any input is processed, so $0 hasn’t been initialised yet.

The END block doesn’t do anything to $0, which keeps its last value. In your AWK script, that’s just the last line read, because AWK reads all its input line by line, does its usual field-splitting processing (assigning $0 and so on), but never finds a matching block; but for example

seq 42 | awk '{ $0 = "21" } END { print }'

outputs 21, not 42, so it’s not the case that “when the END block is run the last line is loaded in $0”.

This isn’t documented in the gawk(1) manpage, but it is documented in mawk(1) (for that implementation of AWK obviously):

Similarly, on entry to the END actions, $0, the fields and NF have their value unaltered from the last record.

The GNU AWK manual does mention this behaviour:

In fact, all of BWK awk, mawk, and gawk preserve the value of $0 for use in END rules.

“BWK awk” is Brian Kernighan’s awk, the “one true awk; it implemented this behaviour in 2005, as documented in its FIXES file:

Apr 24, 2005: modified lib.c so that values of $0 et al are preserved in the END block, apparently as required by posix. thanks to havard eidnes for the report and code.

That change is visible in the “one true awk” history. The latest release of BWK awk behaves in the same way as GNU AWK:

$ echo three fields here | ./awk '{ $0 = "one" } END { print $0 " " NF }'
one 1
$ echo three fields here | ./awk 'END { $0 = "one"; print $0 " " NF }'
one 1
4
  • Thank you for the clarification! Are you able to find references in the manual for that behaviour?
    – mbigras
    Commented Apr 6, 2017 at 22:32
  • There are version of awk (nawk?) that will reset $0 in the END block (IIRC).
    – user14755
    Commented Apr 7, 2017 at 0:59
  • @mbigras I can’t find any reference to END v. $0 processing in Dougherty and Robbins’ sed & awk; I don’t have the original AWK book to check that. I’ll look through the GNU AWK documentation... Commented Apr 7, 2017 at 7:22
  • @mbigras It turns out mawk’s manpage mentions this, as does the GNU AWK documentation (which ilkkachu had already quoted). Commented Apr 7, 2017 at 7:48
4

According to the GNU awk manual, it's slightly unclear what $0 should contain in an END rule. POSIX demands that NF "shall retain [its] value"(*), but doesn't mention $0.

Most probably due to an oversight, the standard does not say that $0 is also preserved, although logically one would think that it should be. In fact, all of BWK awk, mawk, and gawk preserve the value of $0 for use in END rules. Be aware, however, that some other implementations and many older versions of Unix awk do not.

In a sense, I find this behaviour logical. Leaving $0 for the END block allows for easy access to the last record, if necessary. The first record is easy to access with NR == 1 {...} so doesn't need a special keyword. On the other hand, executing BEGIN blocks before loading the first record allows setting FS or RS in time for them to be active for the first record.

(* Whatever that means, see comments.)

10
  • POSIX says “Inside an END action, NF shall retain the value it had for the last record read”, which might be subject to interpretation. GNU AWK at least just keeps the value from the end of the last block that was processed; seq 42 | awk '{ $0 = "2 1" } END { print NF }' outputs “2”. Commented Apr 6, 2017 at 21:30
  • @StephenKitt, well, yes... assigning to $0 updates NF, so I'm not sure what the difference here is.
    – ilkkachu
    Commented Apr 6, 2017 at 21:37
  • When you say “POSIX demands that NF contain the number of fields in the last record”, I understand that as meaning that NF must contain the number of fields in the last record as it was read, not the number of fields in the last value assigned to $0. The same applies for POSIX, even more so since “read” is explicit: does it mean the value assigned when the last record was read, or the value NF had when the last record was finished processing? Commented Apr 6, 2017 at 21:41
  • echo three fields here | awk '{ $0 = "one" } END { print $0 " " NF }' #=> one 3 seems like NF doesn't get recalculated
    – mbigras
    Commented Apr 6, 2017 at 22:30
  • @mbigras What version of AWK are you using? gawk prints “one 1” on my system. Commented Apr 7, 2017 at 4:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .