I have a file with many rows, and each row has a timestamp at the starting, like

[Thread-3] (21/09/12 06:17:38:672) logged message from code.....

So, I frequently check 2 things from this log file.

  1. First few rows, that has the global conditions and start time is also given.
  2. Last few rows, that has the exit status with some other info.

Is there any quick handy single command that could let me display just the first and last few lines of a file?

  • 2
    What's global conditions, and doesn't head and tail works for you?
    – daisy
    Commented Sep 21, 2012 at 9:40
  • That is the part of my log file. I was trying to be elaborative. You can ignore that.
    – mtk
    Commented Sep 21, 2012 at 10:00
  • Your solution looks fine to me. If you want more convenience, make it into a shell function (even an alias might do).
    – vonbrand
    Commented Feb 28, 2013 at 14:33
  • @vonbrand Problem is that I don't know N
    – Bernhard
    Commented Feb 28, 2013 at 14:36
  • @Bernhard, I'm no sed(1) expert, but there are ways of stashing stuff away for later use with it. Maybe it pays off to look in there. OTOH, I'd probably whip up a Perl (or whatever) script to do it if used frequently, as I'm more familiar with that.
    – vonbrand
    Commented Feb 28, 2013 at 14:41

9 Answers 9


@rush is right about using head + tail being more efficient for large files, but for small files (< 20 lines), some lines may be output twice.

{ head; tail;} < /path/to/file

would be equally efficient, but wouldn't have the problem above.

  • In contrast to rushs solution, this does not work in a POSIX shell.
    – Marco
    Commented Feb 28, 2013 at 14:51
  • 2
    @Marco Huh? Only POSIX constructs are used here. What do you see going wrong? Commented Feb 28, 2013 at 15:10
  • 2
    @Gilles I missed the space: {head; tail;} < file works in zsh but fails in sh. { head; tail;} < file always works. Sorry for the noise.
    – Marco
    Commented Feb 28, 2013 at 15:39
  • @Marco, if there were problems with that, it would be with head, not the shell. POSIX requires head to leave the cursor in the file just past those 10 lines for regular files. A problem could arise for non-POSIX head implementations (very old versions of GNU head used to be non-conformant in that instance, but we're talking decades) or if the file is not seekable (like named pipe or socket, but then the other solution would have the same problem). Commented Feb 28, 2013 at 15:41
  • 1
    @FCTW, sudo sh -c '{ head; tail;} < /path/to/file' Commented Mar 10, 2016 at 15:08

You can use sed or awk to make it with one command. However you'll loose at speed, cause sed and awk will need to run through the whole file anyway. From a speed point of view it's much better to make a function or every time to combination of tail + head. This does have the downside of not working if the input is a pipe, however you can use proccess substitution, in case your shell supports it (look at example below).

first_last () {
    head -n 10 -- "$1"
    tail -n 10 -- "$1"

and just launch it as

first_last "/path/to/file_to_process"

to proceed with process substitution (bash, zsh, ksh like shells only):

first_last <( command )

ps. you can even add a grep to check if your "global conditions" exist.

  • -n 10 is the default, no?
    – l0b0
    Commented Mar 1, 2013 at 9:54
  • @l0b0 yes, it's default. -n 10 is not necessary here.
    – rush
    Commented Mar 1, 2013 at 10:35

The { head; tail; } solution wouldn't work on pipes (or sockets or any other non-seekable files) because head could consume too much data as it reads by blocks and can't seek back on a pipe potentially leaving the cursor inside the file beyond what tail is meant to select.

So, you could use a tool that reads one character at a time like the shell's read (here using a function that takes the number of head lines and tail lines as arguments).

head_tail() {
  while [ "$n" -lt "$1" ]; do
    IFS= read -r line || { printf %s "$line"; break; }
    printf '%s\n' "$line"
    n=$(($n + 1))
  tail -n "${2-$1}"
seq 100 | head_tail 5 10
seq 20 | head_tail 5

or implement tail in awk for instance as:

head_tail() {
  awk -v h="$1" -v t="${2-$1}" '
      if(n <= h) n = h+1
      for (;n<=NR;n++) print l[n%t]

With sed:

head_tail() {
  sed -e "1,${1}b" -e :1 -e "$(($1+${2-$1})),\$!{N;b1" -e '}' -e 'N;D'

(though beware that some sed implementations have a low limitation on the size of their pattern space, so would fail for big values of the number of tail lines).


With the -u (--unbuffered) option of GNU sed, you can use sed -u 2q as an unbuffered alternative to head -n2:

$ seq 100|(sed -u 2q;tail -n2)

When (head -n2;tail -n2) is used in a pipeline, it doesn't print the last lines when the last lines are part of a block of input that is consumed by head:

$ seq 1000|(head -n2;tail -n2)
$ seq 100|(head -n2;tail -n2)

If the input is a regular file and not a pipeline, you can just use (head -n2;tail -n2):

$ seq 100 >a;(head -n2;tail -n2)<a
  • 2
    this should be the top answer! works like a charm!
    – Ben Usman
    Commented Aug 10, 2017 at 19:45

Using bash process substitution, you can do the following:

make_some_output | tee >(tail -n 2) >(head -n 2; cat >/dev/null) >/dev/null

Note that the lines are not guaranteed to be in order, though for files longer than about 8kB, they very likely will be. This 8kB cutoff is the typical size of the read buffer, and is related to the reason | {head; tail;} doesn't work for small files.

The cat >/dev/null is necessary to keep the head pipeline alive. Otherwise tee will quit early, and while you'll get output from tail, it'll be from somewhere in the middle of the input, rather than the end.

Finally, why the >/dev/null instead of, say, moving tail to another |? In the following case:

make_some_output | tee >(head -n 2; cat >/dev/null) | tail -n 2  # doesn't work

head's stdout is fed into the pipe to tail rather than the console, which isn't what we want at all.

  • When head or tail finish writing the output they want, they close their stdin and exit. That's where the SIGPIPE is coming from. Normally this is a good thing, they're discarding the rest of the output, so there is no reason for the other side of the pipe to continue spending time generating it.
    – derobert
    Commented Feb 28, 2013 at 16:12
  • What makes the order likely to be upheld? It probably will be for a large file, because tail has to work longer, but I expect (and do see) it failing about half the time for short inputs. Commented Feb 28, 2013 at 16:20
  • You'll get the SIGPIPE with tee >(head) >(tail) for the same reasons (>(...) which by the way is a ksh feature now supported by both zsh and bash as well) uses pipes as well. You could do ... | (trap '' PIPE; tee >(head) >(tail) > /dev/null) but you'll still see some broken pipes error messages from tee. Commented Feb 28, 2013 at 16:28
  • On my system (bash 4.2.37, coreutils 8.13), tail is the one being killed by SIGPIPE, not tee, and tail isn't writing to a pipe. So it must be from a kill(), right?. And this only happens when I'm using the | syntax. strace says that tee isn't calling kill()... so maybe bash?
    – Jander
    Commented Feb 28, 2013 at 16:38
  • 1
    @Jander, try feeding more than 8k like seq 100000 | tee >(head -n1) >(tail -n1) > /dev/null Commented Feb 28, 2013 at 17:39

Using ed (which will read the entire file into RAM though):

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' 'H' '1,10p' '$-10,$p' 'q' | ed -s file
  • Shorter: ed -s file <<< $'11,$-10d\n,p\nq\n' Commented Dec 20, 2014 at 2:18

Stephane's first solution in a function so that you can use arguments (works in any Bourne-like or POSIX shell):

head_tail() {
    head "$@";
    tail "$@";

Now you can do this:

head_tail -n 5 < /path/to/file

This of course assumes that you're looking at only one file and like Stephane's solution works (reliably) only on regular (seekable) files.


I ran into something like this today where I needed only the last line and a few lines from the front of a stream and came up with the following.

sed -n -e '1{h}' -e '2,3{H}' -e '${H;x;p}'

I read this as: initialize the hold space with the contents of the first line, append lines 2-3 in the hold space, at EOF append the last line to the hold space, swap hold-and-pattern space, and print the pattern space.

Perhaps someone with more sed-fu than I have can figure out how to generalize this to print the last few lines of the stream indicated in this question but I didn't need it and could not find an easy way to do math based on the $ address in sed or perhaps by managing the hold space so that only the last few lines are in it when EOF is reached.

  • This is basically the same as sed -n '1,3p;$p'.
    – nisetama
    Commented Mar 23, 2021 at 10:47

You may try Perl, if you have it installed:

perl -e '@_ = <>; @_=@_[0, -3..-1]; print @_'

This will work for most files, but reads the whole file into memory before processing it. If you're not familiar with Perl slices, "0" in square brackets means "take the first line", and "-3...-1" means "take last three lines". You may tailor both of them to your needs. If you need to process really large files (what is 'large' may depend on your RAM and perhaps swap sizes), you may want to go for:

perl -e 'while($_=<>){@_=(@_,$_)[0,-3..-1]}; print @_'

it may be somewhat slower, because it makes a slice every iteration, but it's independent on the file size.

Both commands should work both in pipes and with regular files.

You must log in to answer this question.

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