72

Is there a way to head/tail a document and get the reverse output; because you don't know how many lines there are in a document?

I.e. I just want to get everything but the first 2 lines of foo.txt to append to another document.

Stéphane Gimenez
  • 28,527
  • 3
  • 76
  • 87
chrisjlee
  • 8,283
  • 16
  • 49
  • 54

10 Answers10

72

You can use this to strip the first two lines:

tail -n +3 foo.txt

and this to strip the last two lines, if your implementation of head supports it:

head -n -2 foo.txt

(assuming the file ends with \n for the latter)


Just like for the standard usage of tail and head these operations are not destructive. Use >out.txt if you want to redirect the output to some new file:

tail -n +3 foo.txt >out.txt

In the case out.txt already exists, it will overwrite this file. Use >>out.txt instead of >out.txt if you'd rather have the output appended to out.txt.

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
Stéphane Gimenez
  • 28,527
  • 3
  • 76
  • 87
  • 3
    re *"head, when file ends with `\n`"*.. It works for all negative integers other than `-n -0` which returns *nothing at all*, just as `-n 0` would (using: head (GNU coreutils) 7.4) ... However when a trailing `\n` is present, `-n -0` does print as might be expected from the `-`, ie. it prints the entire file... So it does work for all non-zero negative values.. but `-0` fails when there is no trailing `\n` – Peter.O Aug 16 '11 at 06:16
  • @fred: Strange indeed… (same with 8.12 here). – Stéphane Gimenez Aug 16 '11 at 09:21
  • Is this operation destructive? As i want it to copy the inverse of the first two lines of the document to another? – chrisjlee Aug 16 '11 at 14:29
  • @Chris: No, they just print the result on their "standard output", which is usually connected to the terminal. I've added some details on how to redirect the output to some files. – Stéphane Gimenez Aug 16 '11 at 15:05
  • 11
    `head -n -2` is not [POSIX compatible](http://pubs.opengroup.org/onlinepubs/009604499/utilities/head.html). – l0b0 May 15 '13 at 13:56
  • 1
    `head -n -2 foo.txt` says `head: illegal line count -- -2` – gravitation Jul 10 '14 at 16:45
12

If you want all but the first N-1 lines, call tail with the number of lines +N. (The number is the number of the first line you want to retain, starting at 1, i.e. +1 means start at the top, +2 means skip one line and so on).

tail -n +3 foo.txt >>other-document

There's no easy, portable way to skip the last N lines. GNU head allows head -n +N as a counterpart of tail -n +N. Otherwise, if you have tac (e.g. GNU or Busybox), you can combine it with tail:

tac | tail -n +3 | tac

Portably, you can use an awk filter (untested):

awk -vskip=2 '{
    lines[NR] = $0;
    if (NR > skip) print lines[NR-skip];
    delete lines[NR-skip];
}'

If you want to remove the last few lines from a large file, you can determine the byte offset of the piece to truncate then perform the truncation with dd.

total=$(wc -c < /file/to/truncate)
chop=$(tail -n 42 /file/to/truncate | wc -c)
dd if=/dev/null of=/file/to/truncate seek=1 bs="$((total-chop))"

You can't truncate a file in place at the beginning, though if you need to remove the first few lines of a huge file, you can move the contents around.

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • On some systems (like modern Linux), you can truncate (collapse) a file in place at the beginning, but usually only by an amount that is multiple of the FS block size (so not really useful in this case). – Stéphane Chazelas May 19 '15 at 16:49
4

To remove the first n lines GNU sed can be used. For example if n = 2

sed -n '1,2!p' input-file

The ! mean "exclude this interval". As you can imagine, more complicated result can be obtained, for example

sed -n '3,5p;7p'

that will show line 3,4,5,7. More power come from use of regular expressions instead of addresses.

The limitation is that the lines numbers must be known in advance.

enzotib
  • 50,671
  • 14
  • 120
  • 105
  • 1
    Why not just `sed 1,2d`? Simpler is usually better. Also, nothing in your examples is specific to GNU Sed; your commands all use standard [POSIX features of Sed](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html). – Wildcard Oct 12 '16 at 01:16
3

From the tail man page (GNU tail, that is):

-n, --lines=K
   output the last K lines, instead of the last 10; or use -n +K to
   output lines starting with the Kth

Thus, the following should append all but the first 2 lines of somefile.txt to anotherfile.txt:

tail --lines=+3 somefile.txt >> anotherfile.txt
Steven Monday
  • 1,458
  • 12
  • 11
3
{   head -n2 >/dev/null
    cat  >> other_document
}   <infile

If <infile is a regular, lseek()-able file, then yes, by all means, feel free. The above is a fully POSIXly supported construct.

mikeserv
  • 57,448
  • 9
  • 113
  • 229
1

While tail -n +4 to output the file starting at the 4th line (all but the first 3 lines) is standard and portable, its head counterpart (head -n -3, all but the last 3 lines) is not.

Portably, you'd do:

sed '$d' | sed '$d' | sed '$d'

Or:

sed -ne :1 -e '1,3{N;b1' -e '}' -e 'P;N;D'

(beware that on some systems where sed has a pattern space of limited size, that doesn't scale to large values of n).

Or:

awk 'NR>3 {print l[NR%3]}; {l[NR%3]=$0}'
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
1

You can use diff to compare the output of head/tail to the original file and then remove what is the same, therefore getting the inverse.

diff --unchanged-group-format='' foo.txt <(head -2 foo.txt)
sokoban
  • 11
  • 1
0

Hope I clearly understood your need.

You've several ways to complete your request :

tail -n$(expr $(cat /etc/passwd|wc -l) - 2) /etc/passwd

Where /etc/passwd is your file

The 2nd solution may be usefull if you have huge file:

my1stline=$(head -n1 /etc/passwd)
my2ndline=$(head -n2 /etc/passwd|grep -v "$my1stline")
cat /etc/passwd |grep -Ev "$my1stline|$my2ndline"
Rui F Ribeiro
  • 55,929
  • 26
  • 146
  • 227
0

My approach is similar to Gilles but I instead just reverse the file with cat and pipe that with the head command.

tac -r thefile.txt | head thisfile.txt (replaces files)

Abe
  • 101
  • 2
0

Solution for BSD (macOS):

Remove first 2 lines:

tail -n $( echo "$(cat foo.txt | wc -l)-2" | bc )

Remove last 2 lines:

head -n $( echo "$(cat foo.txt | wc -l)-2" | bc )

...not very elegant but gets the job done!

spectrum
  • 203
  • 2
  • 7