37

I am learning awk/gawk. So recently I just try to solve any problem with it to gain more practice opportunities.

My coworker asked a question yesterday,

"how to remove first and last line from file"

. I know that sed '1d;$d' file would work. also head/tail works too even if the poor performance. I told him the sed one, he was happy.

Later, I was trying to wrote an awk one-liner: so far what I got is:

awk 'NR>1{a[++k]=$0}END{for(i=1;i<k;i++)print a[i]}'

This will store whole file in array just to skip the last line. I feel that there should be an easier(or better) way to do that..

(if there is no easier or faster or better way, I would remove the question)

codeforester
  • 39,467
  • 16
  • 112
  • 140
Imagination
  • 596
  • 1
  • 5
  • 12

3 Answers3

82

This does the trick:

awk 'NR>2 {print last} {last=$0}'

awk executes the action print last only when NR > 2 (that is, on all lines but the first 2). On all lines, it sets the variable last to the current line. So when awk reads the third line, it prints line 2 (which was stored in last). When it reads the last line (line n) it prints the content of line n-1. The net effect is that lines 2 through n-1 are printed.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • 1
    YES!!! I though of this way too, but gave `NR>1`..then gave up...urrrr. didn't think about NR>2 at all, how stupid!.. great! thank you! up! – Imagination Apr 06 '13 at 22:36
  • 1
    Use `FNR` when reading multiple files. See `Two-file processing` in http://backreference.org/2010/02/10/idiomatic-awk/ – Aditya Feb 06 '18 at 22:24
  • `gawk -i inplace 'NR>2 {print last} {last=$0}' file` if someone wants in-place edits. Requires GNU awk 4.1.0+. – Xeverous Jul 13 '21 at 10:22
  • @Xeverous `-i` is a minor abomination. As long as you remember that the file is not edited "in place" at all but is replaced by a new file, it can be a useful feature. But it remains an abomination. – William Pursell Jul 13 '21 at 10:47
  • Note that if it *did* actually edit the file in place, its status would be escalated to major abomination. – William Pursell Jul 13 '21 at 10:48
  • @WilliamPursell I get it (same as `-r` for `grep` - both are against unix philosophy of 1 job per program) but why would proper in-place be a major abomination? Some tools (e.g. `rsync`) actually work in-place for efficiency. – Xeverous Jul 13 '21 at 11:00
  • @Xeverous I dislike `-i` because of the inherent risk of data corruption. `rsync` does not work in place; it transfers data to another box. – William Pursell Jul 13 '21 at 11:04
23

Let me suggest another solution. In case if you need custom N for top and bottom lines you can use tail and head commands:

awk '{print $1}'  | head -n -1 | tail -n+2

head -n -1 - removes last line

tail -n+2 - starts output from second line (removes 1 line)

Following command will remove 3 lines from top and bottom:

awk '{print $1}'  | head -n -3 | tail -n +4

Actually we don't even need awk here:

more | head -n -1 | tail -n +2

or

cat | head -n -1 | tail -n +2

Thanks to Igor Fobia for comment!

Community
  • 1
  • 1
0x8BADF00D
  • 7,138
  • 2
  • 41
  • 34
  • 2
    I like this solution, but now awk is superfluous. You just need to do `more filename | head -n -1 | tail -n +2` – Dr Fabio Gori Dec 16 '15 at 10:10
  • negative number for `head -n` is not POSIX compliant. So this might not work depending on the platform. https://pubs.opengroup.org/onlinepubs/009695299/utilities/head.html – KFL Sep 24 '22 at 04:29
2

Here is another way, but it required gawk function length:

awk '{firstline=2; cuttail=1; l[NR]=$0} END {for (i=firstline; i<=length(l)-cuttail; i++) print l[i]}'