166

Say you have a txt file, what is the command to view the top 10 lines and bottom 10 lines of file simultaneously?

i.e. if the file is 200 lines long, then view lines 1-10 and 190-200 in one go.

S.R.I
  • 1,860
  • 14
  • 31
toop
  • 10,834
  • 24
  • 66
  • 87

22 Answers22

259

You can simply:

(head; tail) < file.txt

And if you need to uses pipes for some reason then like this:

cat file.txt | (head; tail)

Note: will print duplicated lines if number of lines in file.txt is smaller than default lines of head + default lines of tail.

StackzOfZtuff
  • 2,534
  • 1
  • 28
  • 25
Aleksandra Zalcman
  • 3,352
  • 1
  • 19
  • 19
  • 72
    Strictly speaking, this doesn't give you the tail of the original file, but the tail of the stream after `head` has consumed the first 10 lines of the file. (Compare this with `head < file.txt; tail < file.txt` on a file with fewer than 20 lines). Just a very minor point to keep in mind. (But still +1.) – chepner Mar 30 '12 at 17:20
  • 20
    Nice. If you want a gap between the head and tail parts: (head;echo;tail) < file.txt – Simon Hibbs May 23 '12 at 11:02
  • 6
    Curious about why/how this works. Asked it as a new question: http://stackoverflow.com/questions/13718242/ – zellyn Dec 05 '12 at 07:24
  • 1
    @chepner: [`{ tee >(head >&2) | tail; } 2>&1 < file.txt` instead of `{ head; tail; } < file.txt` supports overlapping lines](http://stackoverflow.com/a/20388399/4279) if you need it. `{}` instead of `()` are to avoid creating a subshell. – jfs Dec 05 '13 at 00:13
  • addition to @chepner 's comment: `(head;less) < file.txt` will show the remaining lines (line 11+) after `head` – nametal Jan 22 '16 at 23:02
  • 10
    @nametal Actually, you might not even get that much. While `head` only *displays* the first 10 lines of its input, there is no guaranteed that it didn't *consume* more of it in order to find the 10th line ending, leaving less of the input for `less` to display. – chepner Feb 12 '16 at 16:58
  • 38
    Sorry to say, but the answer only works in some cases. `seq 100 | (head; tail)` gives me only first 10 numbers. Only on much larger input size (like `seq 2000`) the tail gets some input. – modular Sep 21 '16 at 18:57
  • I use it to check time from log file. `(head -n1; tail -n1) < file.txt` – where23 Mar 20 '17 at 08:00
  • 1
    Maybe this is possible : `cat file.txt | (head; tail)` – plhn Apr 26 '17 at 05:57
  • 5
    @alkar @J.F.Sebastian `seq 100 | { tee >(head >&2) | tail; } 2>&1;` also works to avoid the problem that we don't know how much head consumes from the stream – Josiah Yoder Apr 27 '17 at 17:14
  • If you want to avoid duplication of lines see https://stackoverflow.com/a/44849814/99834 – sorin Jun 30 '17 at 15:19
  • @chepner I get the first and last line with `(head -n1; tail -1)`: `cat FILE | (head -n1; tail -1)`. What system are you running these on/are these the GNU versions of the above commands? – Alexej Magura Aug 17 '17 at 21:57
  • @chepner Note: I still get the actual first and last line even with `(head; tail) < FILE`. I'm using GNU coreutils version 8.4 – Alexej Magura Aug 17 '17 at 22:05
  • this is not working for me on a 89 lines text file on macos and I have tried both the native `head`/`tail` and also `ghead` and `gtail`. Only `head` is printing some text. – bennythejudge Sep 28 '18 at 07:23
  • 4
    Note: The buffering issue (head swallows to much from the input stream on small files so that no tail is printed) is fixed by an answer below: `cat file.txt | (sed -u 10q ; echo ; tail)` – haui Oct 23 '19 at 09:53
  • This solution seems to sometimes work and sometimes not. So far the `sed -u` solution mentioned in the comments here and in one of the answers, appears to work for me. – steveb Mar 29 '22 at 03:17
  • Hi @modular, this is a buffering issue and can be avoided with `stdbuf -oL -eL seq 100 | (head; tail)` which enables line buffering for STDOUT and STDERR. See https://unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe – FuePi May 23 '23 at 12:54
25

ed is the standard text editor

$ echo -e '1+10,$-10d\n%p' | ed -s file.txt
kev
  • 155,172
  • 47
  • 273
  • 272
21
(sed -u 10q; echo ...; tail) < file.txt

Just another variation on the (head;tail) theme, but avoiding the initial buffer fill issue for small files.

guest
  • 211
  • 2
  • 2
  • This worked best for me, but for the Mac, I had to `brew install gnu-sed` to get this to work. – steveb Jul 18 '23 at 17:46
20

For a pure stream (e.g. output from a command), you can use 'tee' to fork the stream and send one stream to head and one to tail. This requires using either the '>( list )' feature of bash (+ /dev/fd/N):

( COMMAND | tee /dev/fd/3 | head ) 3> >( tail )

or using /dev/fd/N (or /dev/stderr) plus subshells with complicated redirection:

( ( seq 1 100 | tee /dev/fd/2 | head 1>&3 ) 2>&1 | tail ) 3>&1
( ( seq 1 100 | tee /dev/stderr | head 1>&3 ) 2>&1 | tail ) 3>&1

(Neither of these will work in csh or tcsh.)

For something with a little better control, you can use this perl command:

COMMAND | perl -e 'my $size = 10; my @buf = (); while (<>) { print if $. <= $size; push(@buf, $_); if ( @buf > $size ) { shift(@buf); } } print "------\n"; print @buf;'
RantingNerd
  • 201
  • 2
  • 2
  • 1
    +1 for stream support. You could reuse stderr: `COMMAND | { tee >(head >&2) | tail; } |& other_commands` – jfs Dec 05 '13 at 00:07
  • 2
    btw, it breaks for files larger than the buffer size (8K on my system). `cat >/dev/null` fixes it: `COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands` – jfs Dec 05 '13 at 00:47
  • I loved the solution, but after playing for a a while I noticed that in some cases the tail was running before the head ... there are no guaranteed ordering between `head` and `tail` commands :\ ... – Jan Oct 17 '19 at 05:30
7

Based on J.F. Sebastian's comment:

cat file | { tee >(head >&3; cat >/dev/null) | tail; } 3>&1

This way you can process first line and the rest differently in one pipe, which is useful for working with CSV data:

{ echo N; seq 3;} | { tee >(head -n1 | sed 's/$/*2/' >&3; cat >/dev/null) | tail -n+2 | awk '{print $1*2}'; } 3>&1
N*2
2
4
6
modular
  • 1,099
  • 9
  • 22
6

It took make a lot of time to end-up with this solution which, seems to be the only one that covered all use cases (so far):

command | tee full.log | stdbuf -i0 -o0 -e0 awk -v offset=${MAX_LINES:-200} \
          '{
               if (NR <= offset) print;
               else {
                   a[NR] = $0;
                   delete a[NR-offset];
                   printf "." > "/dev/stderr"
                   }
           }
           END {
             print "" > "/dev/stderr";
             for(i=NR-offset+1 > offset ? NR-offset+1: offset+1 ;i<=NR;i++)
             { print a[i]}
           }'

Feature list:

  • live output for head (obviously that for tail is not possible)
  • no use of external files
  • progressbar one dot for each line after the MAX_LINES, very useful for long running tasks.
  • progressbar on stderr, assuring that the progress dots are separated from the head+tail (very handy if you want to pipe stdout)
  • avoids possible incorrect logging order due to buffering (stdbuf)
  • avoid duplicating output when total number of lines is smaller than head + tail.
sorin
  • 161,544
  • 178
  • 535
  • 806
5

head -10 file.txt; tail -10 file.txt

Other than that, you'll need to write your own program / script.

mah
  • 39,056
  • 9
  • 76
  • 93
  • 1
    Nice, I have always used `cat` and `head` or `tail` piped, good to know that I can use them individually! – Paul Dec 24 '11 at 13:09
  • How can I then pipe these first 10+last 10 into another command? – toop Dec 24 '11 at 13:12
  • `head file.txt; tail file.txt | your_program` – Paul Dec 24 '11 at 13:15
  • 1
    @Paul - with 'your_program' as wc -l it returns 10 instead of 20 – toop Dec 24 '11 at 13:18
  • `(head .vimrc ; tail .vimrc) | wc -l` – Paul Dec 24 '11 at 13:20
  • 4
    or, without having to spawn a subshell: `{ head file; tail file; } | prog` (spacing inside the braces, and the trailing semicolon are required) – glenn jackman Dec 24 '11 at 19:25
  • 1
    Wow... a down-vote for having an answer quite similar to others (yet timestamped before them) after almost two years, from someone who chose not to post why they down-voted. Nice! – mah Oct 12 '13 at 08:17
5

the problem here is that stream-oriented programs don't know the length of the file in advance (because there might not be one, if it's a real stream).

tools like tail buffer the last n lines seen and wait for the end of the stream, then print.

if you want to do this in a single command (and have it work with any offset, and do not repeat lines if they overlap) you'll have to emulate this behaviour I mentioned.

try this awk:

awk -v offset=10 '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' yourfile
Samus_
  • 2,903
  • 1
  • 23
  • 22
2

I have been looking for this solution for a while. Tried it myself with sed, but the problem with not knowing the length of file/stream beforehand was insurmountable. Of all the options available above, I like Camille Goudeseune's awk solution. He did make a note that his solution left extra blank lines in the output with a sufficiently small data set. Here I provide a modification of his solution that removes the extra lines.

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { a_count=0; for (i in a) {a_count++}; for (i=NR-a_count+1; i<=NR; i++) print a[i] }' ; }
Michael Blahay
  • 131
  • 1
  • 1
  • 6
2

Well, you can always chain them together. Like so, head fiename_foo && tail filename_foo. If that is not sufficient, you could write yourself a bash function in your .profile file or any login file that you use:

head_and_tail() {
    head $1 && tail $1
}

And, later invoke it from your shell prompt: head_and_tail filename_foo.

S.R.I
  • 1,860
  • 14
  • 31
1

I wrote a simple python app to do this: https://gist.github.com/garyvdm/9970522

It handles pipes (streams) as well as files.

Gary van der Merwe
  • 9,134
  • 3
  • 49
  • 80
1

drawing on ideas above (tested bash & zsh)

but using an alias 'hat' Head and Tails

alias hat='(head -5 && echo "^^^------vvv" && tail -5) < '


hat large.sql
zzapper
  • 4,743
  • 5
  • 48
  • 45
1

Consumes stdin, but simple and works for 99% of use cases

head_and_tail

#!/usr/bin/env bash
COUNT=${1:-10}
IT=$(cat /dev/stdin)
echo "$IT" | head -n$COUNT
echo "..."
echo "$IT" | tail -n$COUNT

example

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
Brad Parks
  • 66,836
  • 64
  • 257
  • 336
  • I was searching for a head and tail solution, after created an one-liner. I enhanced this solution to set -n see: https://github.com/rundekugel/myScripts/blob/master/hat.sh – rundekugel Feb 23 '23 at 10:01
1

First 10 lines of file.ext, then its last 10 lines:

cat file.ext | head -10 && cat file.ext | tail -10

Last 10 lines of the file, then the first 10:

cat file.ext | tail -10 && cat file.ext | head -10

You can then pipe the output elsewhere too:

(cat file.ext | head -10 && cat file.ext | tail -10 ) | your_program

Paul
  • 20,883
  • 7
  • 57
  • 74
  • 6
    Why use cat when you can just call head -10 file.txt? – jstarek Dec 24 '11 at 14:52
  • Can you make the number of lines variable, so the call is something like: head_ tail(foo, m,n) - returning the first m snd last n lines of text? – ricardo Jul 22 '12 at 20:02
  • @ricardo that would involve writing a bash script that takes 3 args and passes them to `tail` and `head` or a function by alias-ing it. – Paul Jul 23 '12 at 10:19
0

To handle pipes (streams) as well as files, add this to your .bashrc or .profile file:

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' ; }

Then you can not only

headtail 10 < file.txt

but also

a.out | headtail 10

(This still appends spurious blank lines when 10 exceeds the input's length, unlike plain old a.out | (head; tail). Thank you, previous answerers.)

Note: headtail 10, not headtail -10.

Camille Goudeseune
  • 2,934
  • 2
  • 35
  • 56
0

Building on what @Samus_ explained here about how @Aleksandra Zalcman's command works, this variation is handy when you can't quickly spot where the tail begins without counting lines.

{ head; echo "####################\n...\n####################"; tail; } < file.txt

Or if you start working with something other than 20 lines, a line count might even help.

{ head -n 18; tail -n 14; } < file.txt | cat -n
Script Wolf
  • 106
  • 2
  • 12
0

To print the first 10 and last 10 lines of a file, you could try this:

cat <(head -n10 file.txt) <(tail -n10 file.txt) | less

0
sed -n "1,10p; $(( $(wc -l ${aFile} | grep -oE "^[[:digit:]]+")-9 )),\$p" "${aFile}"

NOTE: The aFile variable contains the file's full path.

mark_infinite
  • 383
  • 1
  • 7
  • 13
0

I would say that depending upon the size of the file, actively reading in its contents may not be desirable. In that circumstance, I think some simple shell scripting should suffice.

Here's how I recently handled this for a number of very large CSV files that I was analyzing:

$ for file in *.csv; do echo "### ${file}" && head ${file} && echo ... && tail ${file} && echo; done

This prints out the first 10 lines and the last 10 lines of each file, while also printing out the filename and some ellipsis before and after.

For a single large file, you could simply run the following for the same effect:

$ head somefile.csv && echo ... && tail somefile.csv
Jitsusama
  • 617
  • 7
  • 20
0

I did some more experimenting based mostly on suggestions here. After working a bit, I up with something very similar to another version in another comment, but with a focus more on formatting with multiple file args in addition to stdin.

This wraps nicely into a script (tentatively headtail) and uses the gnu awk. On macOs this can be installed via brew install gawk.

It can work on piped content OR a list of files as arguments. Given files, it prints a header of the file name, the head N lines, a skipped lines maker, then the tail N lines. If the head and tail overlap or would line up, it neither includes a skip marker nor does it display any duplicate lines.

#!/bin/bash
headtail_awk() {
  N=10
  gawk -v "n=${N}" -- '\
  FNR == 1 && FILENAME != "-" {
    printf "\033[036m==> %s <==\033[0m\n", FILENAME;
  }
  # print head lines
  FNR <= n { print }
  # store lines in a circular buffer
  { a[FNR % n]=$0 }
  # print non-overlapping tail lines from circular buffer.
  ENDFILE {
    if ( FNR > 2 * n ) {
      printf "\033[0;36m>>> %s lines skipped <<<\033[0m\n", FNR - 2 * n
    }
    for (i=FNR-n+1;i<=FNR;i++) {
      if ( i > n) {
        print a[i % n]
      }
    }
  }
' "$@"
}
headtail_awk "$@"

I'll leave any getopts and/or enhancements of the N=10 line window as an exercise for the reader.

sample output of multiple files (with n=3):

$ headtail -n 3 /usr/share/dict/words /usr/share/dict/propernames
==> /usr/share/dict/words <==
A
a
aa
>>> 235880 lines skipped <<<
zythum
Zyzomys
Zyzzogeton
==> /usr/share/dict/propernames <==
Aaron
Adam
Adlai
>>> 1302 lines skipped <<<
Wolfgang
Woody
Yvonne
Jeff Adamson
  • 387
  • 2
  • 5
0

this worked for me: (head -100) < source.txt > target.txt

(head -100) < source.txt takes the first 100 lines from the source.txt file and then

taget.txt pushes the 100 lines into a new file called target.txt

Initially I thought something like this should work: head -100 source.txt > target.txt but it returned an empty file.

  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 18 '22 at 10:48
0

Why not to use sed for this task?

sed -n -e 1,+9p -e 190,+9p textfile.txt

lik
  • 219
  • 1
  • 6