74

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

75

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

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). Commented May 19, 2015 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.

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
    Commented Oct 12, 2016 at 1: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
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!

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .