73

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.

chrisjlee
  • 8,523

10 Answers10

73

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.

  • 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. – 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
13

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.

  • 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
  • 51,661
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
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
  • 58,310
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}'
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
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
  • 56,709
  • 26
  • 150
  • 232
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
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
  • 213
  • 3
  • 7