Read Parts of a File
Problem statement
Print only the part of a file you need: the first lines, the last lines, a range in the middle, and the line count. Logs on real servers can be gigabytes, so you almost never print a whole file. You look at the start to see how the app booted, and at the end to see what just happened.
app.log
08:00:01 INFO server starting08:00:02 INFO loading config08:00:03 INFO connected to db08:00:05 WARN cache is cold08:00:07 INFO listening on :808008:01:10 ERROR request timed out08:01:11 INFO retry ok08:02:30 WARN disk 81% full08:03:00 INFO health check ok08:03:30 INFO shutting downPrint, in this order: the first 3 lines, the last 3 lines, the number of lines, lines 5 to 7, and every line from line 9 to the end.
Expected output:
== first 3 lines ==08:00:01 INFO server starting08:00:02 INFO loading config08:00:03 INFO connected to db== last 3 lines ==08:02:30 WARN disk 81% full08:03:00 INFO health check ok08:03:30 INFO shutting down== number of lines ==10== lines 5 to 7 ==08:00:07 INFO listening on :808008:01:10 ERROR request timed out08:01:11 INFO retry ok== from line 9 to the end ==08:03:00 INFO health check ok08:03:30 INFO shutting downHints
head reads from the top and tail reads from the bottom. Both take -n with a number of lines.Approach
Optimal: head, tail, wc and sed
Covers: cat, less, head -n, tail -n, tail -n +K, tail -f, wc -l, sed -n 'A,Bp'.
Pick the tool by the part you want.
head -n 3"]:::blue L4["line 4"]:::gray L5["lines 5 to 7
sed -n '5,7p'"]:::yellow L8["lines 8 to 10
tail -n 3"]:::green L1 ~~~ L4 ~~~ L5 ~~~ L8 end FILE ~~~ W(["wc -l counts all 10 lines"]):::purple classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px style FILE fill:transparent,stroke:#6b7280,stroke-width:2px
| Command | What it prints |
|---|---|
cat file |
the whole file, all at once |
less file |
the file one screen at a time (arrows to move, /word to search, q to quit) |
head -n 3 file |
the first 3 lines |
tail -n 3 file |
the last 3 lines |
tail -n +9 file |
from line 9 to the end (note the +) |
sed -n '5,7p' file |
lines 5 to 7 |
wc -l file |
the number of lines |
Why not just cat? cat prints everything. On a 5 GB log that floods your screen for minutes and tells you nothing. Use less when you want to read a file, because it only loads what is on screen. Use head, tail or sed when you know which part you want.
How sed -n '5,7p' works. sed normally prints every line it reads. -n turns that off, so nothing is printed unless you ask. 5,7 picks lines 5 to 7, and p means print. So the whole command means "print lines 5 to 7 and nothing else".
tail -n 3 vs tail -n +9. Without a +, the number counts from the end: the last 3 lines. With a +, it is a starting line number: line 9 and everything after it. This is handy for skipping a header, for example tail -n +2 data.csv skips line 1.
Why wc -l < app.log and not wc -l app.log? Given a file name, wc prints the number and the name, like 10 app.log. Reading the file through < gives it no name to print, so you get just 10. That is easier to use in a script.
Walking through the code. The # Setup: lines only create the sample log, so skip past them. Then each block prints a title with echo and runs one command. The output matches the file line for line, so you can check each part against the sample above.
Edge cases. If the file has fewer lines than you ask for, head and tail just print what is there, with no error. On an empty file, all of them print nothing and wc -l prints 0. If a range starts past the end, sed prints nothing.
# Setup: create the sample log in a fresh temporary folder
cd "$(mktemp -d)"
cat > app.log << 'LOG'
08:00:01 INFO server starting
08:00:02 INFO loading config
08:00:03 INFO connected to db
08:00:05 WARN cache is cold
08:00:07 INFO listening on :8080
08:01:10 ERROR request timed out
08:01:11 INFO retry ok
08:02:30 WARN disk 81% full
08:03:00 INFO health check ok
08:03:30 INFO shutting down
LOG
echo "== first 3 lines =="
head -n 3 app.log
echo "== last 3 lines =="
tail -n 3 app.log
echo "== number of lines =="
wc -l < app.log
echo "== lines 5 to 7 =="
sed -n '5,7p' app.log
echo "== from line 9 to the end =="
tail -n +9 app.logInterview follow-ups
The log is 50 GB. How do you print line 10 without reading the rest?
sed -n '10p' fileprints line 10, but then keeps reading all 50 GB to the end, looking for more matches.sed -n '10{p;q}' fileprints line 10 and then quits (q), so it reads only the first 10 lines.head -n 10 file | tail -n 1also stops early, becauseheadquits after 10 lines. Both finish instantly, whatever the size of the file. The same idea applies to any "print and stop" task on a huge file.
Frequently asked questions
Both keep the file open and print new lines as they are written, which is how you watch a live log. -f follows the open file itself. When the log is rotated (renamed to app.log.1 and a fresh app.log is created), -f keeps watching the old renamed file and you see nothing new. -F follows the name, so it notices the new file and switches to it. On servers with log rotation, tail -F is the safer habit. Press Ctrl+C to stop either one.
wc -l counts newline characters, not lines you can see. If the last line of a file has no newline at the end, it is not counted. Files made by most editors and tools end with a newline, so this is rare, but files from some programs or from Windows can skip it. grep -c '' counts that last line too, if you need an exact count. Note that macOS wc also pads the number with spaces, which matters when you compare it in a script.
Use less when you do not know yet what you are looking for. It lets you scroll, search with /error, jump to the end with G, and quit with q, all without loading the whole file. Use head or tail when you know you want the start or the end, and in scripts, because less waits for keys. less +F works like tail -f, and Ctrl+C drops you back to normal scrolling.