70

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.

10 Answers 10

71

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.

6
  • 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, 2011 at 6:16
  • @fred: Strange indeed… (same with 8.12 here). Aug 16, 2011 at 9: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, 2011 at 14:29
  • 11
    head -n -2 is not POSIX compatible.
    – l0b0
    May 15, 2013 at 13:56
  • 1
    head -n -2 foo.txt says head: illegal line count -- -2 Jul 10, 2014 at 16:45
11

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.

1
  • 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). May 19, 2015 at 16:49
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

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.

1
  • 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.
    – Wildcard
    Oct 12, 2016 at 1:16
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.

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)
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}'
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)

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"
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!

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.