Sample output
2026-01-15,northeast,widget-a,150,4500.00
2026-01-15,southwest,widget-b,89,2670.00
2026-02-01,northeast,widget-a,200,6000.00
2026-02-01,midwest,widget-c,45,2250.00
2026-03-10,southwest,widget-a,175,5250.00
2026-03-10,northeast,widget-b,120,3600.00
2026-04-22,midwest,widget-a,95,2850.00
2026-04-22,southwest,widget-c,60,3000.00
The date,region,product,units,revenue line is gone and every data row is intact. Pipe this into wc -l and you get 8 sales, not 9. Pipe it into sort -t, -k5 -nr and the header no longer lands somewhere in the middle of the numbers.
When you would reach for it
Any time a header row gets in the way of a tool that does not know what a header is: counting records, sorting by a numeric column, summing with awk, or feeding rows into a while read loop. It also works on command output with a title line, like ps, df, or docker ps, when you want only the rows underneath.
Gotchas
- The count is the first line kept, not the number skipped.
tail -n +1prints the whole file, and so does+0. To drop a three-line preamble, use+4. Off-by-one here silently costs you your first record, so check the first line of output once. tail -n 2andtail -n +2do different things by design. On a regular file, plaintail -n 2seeks to the end and reads backwards, which is why it returns instantly on a 50 GB log.+2has to read forward from the start through every line it skips, so on a large file it costs a full read.headhas no mirror image that works everywhere. GNUhead -n -1drops the last line, but macOS and BusyBoxheadreject negative counts. For a portable version, usesed '$d'.- With more than one file, tail prints a
==> sales.csv <==banner before each one, and those banners end up in your data. Add-qto suppress them, or use theawkvariant below.
Variants
$ tail -qn +2 q*.csv > all-quarters.csv
Stitch several CSVs together without repeating their headers. -q drops the per-file banners. Write the output somewhere the glob cannot match, or it reads its own output
$ { IFS= read -r header; echo "$header"; sort -t, -k5 -nr; } < sales.csv
Keep the header on top and sort only the rows. read consumes exactly one line, then sort gets the rest of the same input. (head -n 1; sort) looks equivalent but breaks on a pipe, because head reads a whole buffer and the rows it does not print are gone
$ awk 'FNR > 1' sales.csv returns.csv
FNR restarts at 1 for each file, so this skips the header of every file with no banners and no -q