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.
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
.
\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
head -n -2 foo.txt
says head: illegal line count -- -2
Commented
Jul 10, 2014 at 16:45
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.
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.
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.
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
{ 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.
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)
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}'
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)
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"
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!