I have several files in the same dir for which I want to print certain lines from a continuous interval, e.g., from 15th to 20th.

For a single file, this works head -n20 file.txt | tail -n6, but how I can make it work for the globbing patterns, e.g., for all txt files in this dir *.txt?

head -n20 *.txt | tail -n6 # this only crops results of head -n20

EDIT1: I am also aware of the workaround with for, but instead I am hoping to learn how to define piping of several operations in a uniform way that also works with wildcards.

p.s. probably having the standard headers, like ==> file.txt <== which head and tail give when combined with wildcards, is too much to ask.

p.p.s. using ubuntu, but a unix-wide approach would be better.

  • 1
    I don't care about such corner cases. Commented Jan 30, 2022 at 17:09

2 Answers 2


What about this oneliner?

for f in *.txt; do echo -e "\n==> $f <=="; head -n 20 "$f" | tail -n 6; done

When run in the currect directory, it loops over all the .txt files using the *.txt glob and then for each file prints a header and does the head, tail thing.

  • Actually, I am already using this option and hoping that there might be a better way to define head-tail piping as a single function. I should have excluded for do option in my OP. Commented Jan 30, 2022 at 16:05
  • No problem, but yes you could have mentioned that :-)
    – Edward
    Commented Jan 30, 2022 at 16:06

Note that while standard head can take more than one filename as argument and outputs those ==> filename <== headers, standard tail can only take one filename as argument (the behaviour is unspecified if you pass more than one.

Here instead of using a shell loop, you could use gawk:

gawk 'BEGINFILE{print sep"==> "substr(FILENAME, 3)" <=="; sep = "\n"}
      FNR >= 15
      FNR == 20 {nextfile}' ./*.txt

You could make it a function:

linerange() (
  min=$1 max=$2
  shift 2
  exec gawk -v min="$min" -v max="$max" -e '
    BEGINFILE{print sep"==> "FILENAME" <=="; sep = "\n"}
    FNR > max {nextfile}
    FNR >= min' -E /dev/null "$@"

And then:

linerange 15 20 *.txt

gawk, like any awk has that problem that an argument in the form of var=value is taken as a variable assignment instead of an input filename. That means that if some of your .txt files are for instance foo=bar.txt that won't work properly (you can have more annoying side effects for things like ARGC=0.txt or ORS=.txt...).

We work around that in the first case by using a ./ prefix (which we strip later with substr(FILENAME, 3) and -E in the second case (passing it an empty file: /dev/null, but using the fact that assignment arguments are not processed when -E is used).

You must log in to answer this question.

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