213

I want to print the second-to-last column or field in awk. The number of fields is the NF variable. I know that I should be able to use $NF, but I'm not sure how it can be used.

And this does not seem to work:

awk ' { print ( $NF-- )  } '
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
Brian G
  • 53,704
  • 58
  • 125
  • 140

10 Answers10

362
awk '{print $(NF-1)}'

Should work

Rushi Agrawal
  • 3,208
  • 2
  • 21
  • 26
Chris Kannon
  • 5,931
  • 4
  • 25
  • 35
  • 2
    This does not work for me. I get "title:5: command not found: NF-1" in awk 3.1.8 under Ubuntu. – Gurgeh May 02 '12 at 11:20
  • 3
    I would avoid the pre/post decrement to make sure you don't change the value of $NF – Gregory Patmore Feb 25 '15 at 16:32
  • This breaks if you try to get the third last field like `awk -F '.' '{print $(NF-2)}'` – gies0r Jun 06 '18 at 08:01
  • 4
    @Gurgeh: This happens because `$(..)` invokes a command in a subshell depending on which shell you're using. You can work around this by using `$ (NF-1)` instead of `$(NF-1)`. – wting Oct 14 '18 at 16:16
  • @wting : ur interpretation is ***entirely*** off the mark - there's nothing related to the shell here - `$(…)` is just a way to grouping operations inside awk that yields an outcome that could be evaluated to a certain field number. you can do `$( atan2(0, -1) )` if you like - it'll simply become `$(3.14159…)` which is then auto truncated to `$3`. `gawk` and `nawk` are even willing to accept `+/- nan` as a valid `NF` – RARE Kpop Manifesto Jul 09 '22 at 16:22
  • @RAREKpopManifesto wting's interpretation looks right to me. "command not found" is the result you'd expect if `$(NF-1)` were interpreted by the shell. Which might happen if e.g. you omitted the `'` marks from the above command. – mwfearnley Jun 13 '23 at 11:46
  • @mwfearnley : if single quotes were missing then of course all typical assumptions are out the window. while `awk` is a very forgiving language, syntactically, I try avoid recommending the placement of a space between the sigil and the expression as a bandaid solution – RARE Kpop Manifesto Jun 14 '23 at 21:38
  • @RAREKpopManifesto perhaps, but Gurgeh's issue was likely because it was the shell interpreting the `$()`, so awk wouldn't even get to see it. wting's solution wasn't necessarily the best, but he was right about the shell. – mwfearnley Jun 15 '23 at 09:06
  • @mwfearnley : not so much about solution being optimal or not - I was concerned it would do more long term harm to the user by giving the impression that gapped sigils are common and widely accepted practices, and gapped sigils exhibit strange behavior on their own - say in `perl 5`, they accept `$ _` but not `$ (_)`. `awk` accepts both , and `_` isn't a reserved variable at all in `awk`. even worse is like ` $ "0" --NF` : despite it's seemingly obvious appearance, it doesn't decrement `NF` by one, but actually decremented whatever is on the entire row of `$0`……. – RARE Kpop Manifesto Jun 15 '23 at 10:36
  • @mwfearnley : just cuz `awk` is being so lexicographically lenient it's willing to accept ***this*** as valid syntax ::::: `print $ $ $ $ $ _ ^= $ $ ! $ !_;` ….. doesn't mean we should be suggesting this multi-layered redirection style of gapped sigils to anyone. – RARE Kpop Manifesto Jun 15 '23 at 10:43
  • OK, but this wouldn't be an issue with interpreting the problem, but with the solution suggested. – mwfearnley Jun 15 '23 at 11:31
25

Small addition to Chris Kannon' accepted answer: only print if there actually is a second last column.

(
echo       | awk 'NF && NF-1 { print ( $(NF-1) ) }'
echo 1     | awk 'NF && NF-1 { print ( $(NF-1) ) }'
echo 1 2   | awk 'NF && NF-1 { print ( $(NF-1) ) }'
echo 1 2 3 | awk 'NF && NF-1 { print ( $(NF-1) ) }'
)
progz
  • 309
  • 3
  • 2
22

It's simplest:

 awk '{print $--NF}' 

The reason the original $NF-- didn't work is because the expression is evaluated before the decrement, whereas my prefix decrement is performed before evaluation.

Birei
  • 35,723
  • 2
  • 77
  • 82
Steve
  • 221
  • 2
  • 3
  • As a mnemonic, this behavior is the same as C/Java etc. `int x = ++i` `int x = i++`, prefix means increment first; postfix means increment later (assignment first). – Weekend Dec 11 '19 at 07:20
11
awk ' { print ( $(NF-1) ) }' file
Steve
  • 9,270
  • 5
  • 47
  • 61
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
8

You weren't far from the result! This does it:

awk '{NF--; print $NF}' file

This decrements the number of fields in one, so that $NF contains the former penultimate.

Test

Let's generate some numbers and print them on groups of 5:

$ seq 12 | xargs -n5
1 2 3 4 5
6 7 8 9 10
11 12

Let's print the penultimate on each line:

$ seq 12 | xargs -n5 | awk '{NF--; print $NF}'
4
9
11
fedorqui
  • 275,237
  • 103
  • 548
  • 598
6

First decrements the value and then print it -

awk ' { print $(--NF)}' file

OR

rev file|cut -d ' ' -f2|rev
VIPIN KUMAR
  • 3,019
  • 1
  • 23
  • 34
4

Perl solution similar to Chris Kannon's awk solution:

perl -lane 'print $F[$#F-1]' file

These command-line options are used:

  • n loop around every line of the input file, do not automatically print every line

  • l removes newlines before processing, and adds them back in afterwards

  • a autosplit mode – split input lines into the @F array. Defaults to splitting on whitespace

  • e execute the perl code

The @F autosplit array starts at index [0] while awk fields start with $1.
$#F is the number of elements in @F

Chris Koknat
  • 3,305
  • 2
  • 29
  • 30
2

Did you tried to start from right to left by using the rev command ? In this case you just need to print the 2nd column:

seq 12 | xargs -n5 | rev | awk '{ print $2}' | rev
4
9
11
Fdv
  • 385
  • 2
  • 5
  • 14
1

If you have many columns and want to print all but not the three cloumns in the last, then this might help

awk '{ $NF="";$(NF-1)="";$(NF-2)="" ; print $0 }'

san1512
  • 914
  • 1
  • 9
  • 16
0
date
Sat Jul  9 11:57:36 EDT 2022

date | gawk '$_=$--NF'

     | mawk '$!--NF=$NF'
EDT
RARE Kpop Manifesto
  • 2,453
  • 3
  • 11