1

I've put together an example using the AWK getline function and it's confusing me.

$ cat in
foo
bar
baz
$ awk '{ getline tmp; print tmp; print $0 }' in
bar
foo
bar
baz

I'm reading the next line into a variable named tmp which doesn't change $0 as confirmed by the first two lines of output:

bar
foo

This is confirmed by the following table taken from The AWK Programming Language on page 62:

enter image description here

I know that the NR and FNR built-ins represent the number of lines read so far. I think this is the key to understanding what's going on but I'm confused how changing NR while in a pass affects future passes.

I was expecting the next two lines to be:

baz
bar

because on the second pass $0 == bar and tmp == baz.

Then I was expecting the next two lines to really only be one line:

baz

because on the third pass $0 == baz and tmp == null.

So my expected output is:

bar
foo
baz
bar
baz

I think understanding how changing NR while in the awk loop is the key to understanding this output.

  • Can you explain why my expected output is wrong and why the actual output is right?

I'm running awk version 20070501 on macOS 10.12.1

2

I think what you're missing is that, in setting NR, getline in effect consumes the line. So at the second invocation, bar is already gone and $0 is baz; getline attempts to read another line and fails; and the value of tmp remains unchanged (i.e. equal to bar).

It may be easier to understand if you check the return value of getline:

awk '{ if ((getline tmp) > 0) print tmp; print $0 }' in
bar
foo
baz
  • So if I understand right: both $0 and getline tmp use the value of NR to get their values. But getline increases NR and reaching the end of each invocation also increments NR so there really is only 2 invocations! In fact $0 never sees bar, in my case, we see bar twice because it's still inside tmp. Wow, this is very subtle. – mbigras May 26 '17 at 20:31
  • @mbigras yes I think that's correct - although I can't find a reference that actually states that the value of tmp is unmodified if getline fails (rather than, for example, being assigned the empty string). However there are people much more knowledgeable in awk than me on here - let's see if one of them chimes in. – steeldriver May 26 '17 at 20:35
1

It should become clear if you look at the bigger picture, so to speak. An awk program is a loop around the program text, that reads one line then executes the program on this line. If you read a line inside the program, then the surrounding loop doesn't get to see this line: it's already been consumed.

For example, your program

{ getline tmp; print tmp; print $0 }

could be written as

BEGIN {
    while (getline $0) {
        getline tmp; print tmp; print $0
    }
}

The BEGIN block is executed once at the beginning of the program, and here the program doesn't do anything else — of course this is a highly non-idiomatic way of writing awk code.

Here it should be clear that what happens is:

  • Read line 1 to $0 with the first getline
  • Read line 2 to tmp with the second getline
  • Print tmp then $0, i.e. print line 2 then line 1
  • Repeat with the next pair of lines: print line 4 then line 3, etc.

With an odd number of lines, the last line goes through getline $0, then getline tmp fails but you aren't checking the return status so this merely leaves tmp unchanged, and you end up printing the next-to-last line again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.