Home / Learn Linux / Lesson 6
Reading files: cat, less, head and tail
Open small and huge files, jump around inside them, and follow a log as it grows.
- Lesson
- 6 of 30
- Time
- About 30 minutes
On this page
- Set up some files to read
- cat: print a whole file
- less: read big files
- head: the start of a file
- tail: the end of a file
- watch: run something again and again
- Common mistakes
- Practice
- Cheat sheet
Set up some files to read
Make a practice directory with a numbered file and a small fake log. seq prints a range of numbers, and > writes a command's output into a file (lesson 8):
$ mkdir ~/read-lab $ cd ~/read-lab $ seq 1 100 > numbers.txt $ printf "08:00 start\n08:01 ok\n08:02 warning: disk 81%%\n08:03 ok\n08:04 error: disk full\n" > app.log $ ls -l total 8 -rw-rw-r-- 1 ubuntu ubuntu 77 Oct 21 11:36 app.log -rw-rw-r-- 1 ubuntu ubuntu 292 Oct 21 11:36 numbers.txt
numbers.txt has the lines 1 to 100, which makes it easy to see exactly which lines each command prints. Run everything below from ~/read-lab.
cat: print a whole file
cat prints a file to the screen in one go. It's perfect for short files:
$ cat app.log
08:00 start
08:01 ok
08:02 warning: disk 81%
08:03 ok
08:04 error: disk fullGive it several files and it prints them one after the other. That's actually what its name means: con*cat*enate, join together:
$ cat app.log /etc/hostname
08:00 start
08:01 ok
08:02 warning: disk 81%
08:03 ok
08:04 error: disk full
linuxTwo options help when you're looking closely at a file:
-nnumbers every line.-Ashows the invisible characters: a$at the end of every line and^Ifor each tab. It's the quickest way to find trailing spaces or tabs that break a config file.
$ cat -n app.log 1 08:00 start 2 08:01 ok 3 08:02 warning: disk 81% 4 08:03 ok 5 08:04 error: disk full $ printf "name\tvalue \n" > spaces.txt $ cat -A spaces.txt name^Ivalue $
The space before $ is a trailing space you'd never see otherwise.
tac is cat backwards: it prints the last line first. It's handy for a log when you want the newest entries on top:
$ tac app.log
08:04 error: disk full
08:03 ok
08:02 warning: disk 81%
08:01 ok
08:00 startDon't cat a huge file. cat numbers.txt is fine, but cat on a 2 GB log floods your terminal for minutes. Use less, head or tail for anything big.
Try it
- Print
/etc/os-releasewith line numbers. - Print
app.logandnumbers.txttogether. Which comes first?
Answer
$ cat -n /etc/os-release
1 PRETTY_NAME="Ubuntu 26.04.1 LTS"
2 NAME="Ubuntu"
3 VERSION_ID="26.04"
4 VERSION="26.04.1 LTS (Resolute Raccoon)"
5 VERSION_CODENAME=resolute
6 ID=ubuntu
7 ID_LIKE=debian
8 HOME_URL="https://www.ubuntu.com/"
9 SUPPORT_URL="https://help.ubuntu.com/"
10 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
11 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
12 UBUNTU_CODENAME=resolute
13 LOGO=ubuntu-logoFor the second, cat app.log numbers.txt prints app.log first, because cat follows the order you give it.
less: read big files
less opens a file one screen at a time and lets you move around in it, search it, and quit when you're done. It's the same pager man uses, so the keys are the ones from lesson 2:
$ less /usr/share/common-licenses/GPL-3| Key | Does |
|---|---|
Space / b | Next / previous page |
Arrow keys, j / k | One line down / up |
g / G | Start / end of the file |
/text, ?text | Search forward / backward |
n / N | Next / previous match |
&text | Show only the lines containing text (& alone shows all again) |
-N then Enter | Turn line numbers on or off |
F | Follow the file as it grows (Ctrl+C to stop following) |
q | Quit |
less doesn't load the whole file before showing it, so it opens a multi-gigabyte log instantly. Two options are worth knowing:
less -N filestarts with line numbers.less -S filedoesn't wrap long lines, so each line stays on one row and you scroll sideways with the arrow keys. It's great for wide logs and CSV files.
Try it
- Open the GPL licence in
less, search forwarranty, and jump through the matches. - In
less app.log, show only the lines containingdisk.
Answer
less /usr/share/common-licenses/GPL-3, then/warrantyEnter, thennfor each next match, andqto leave.less app.log, then type&diskand Enter. Two lines are left. Type&and Enter to see everything again.
head: the start of a file
head prints the first 10 lines:
$ head numbers.txt
1
2
3
4
5
6
7
8
9
10-n picks how many. With a minus sign in front of the number, it prints everything except the last that many lines:
$ head -n 3 numbers.txt 1 2 3 $ head -n -97 numbers.txt 1 2 3
head -3 is a shorter way to write head -n 3. -c counts bytes instead of lines, so head -c 20 prints the first 20 characters.
Try it
- Print the first 5 lines of
/etc/passwd. - Print everything in
app.logexcept its last line.
Answer
$ head -n 5 /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync $ head -n -1 app.log 08:00 start 08:01 ok 08:02 warning: disk 81% 08:03 ok
tail: the end of a file
tail is the opposite: the last 10 lines. On a log file, the end is where the newest entries are, so tail is what you'll actually run most:
$ tail -n 3 numbers.txt
98
99
100With a plus sign, tail -n +N prints from line N to the end. That's a neat way to skip a header line:
$ tail -n +98 numbers.txt
98
99
100ubuntu is in the adm group (you saw that with groups in lesson 3), and on Ubuntu that group may read most logs in /var/log, so you don't need sudo for this:
$ tail -n 5 /var/log/syslog
2026-10-21T11:35:20.980069+00:00 linux kernel: audit: type=1400 audit(1792582520.978:227): apparmor="DENIED" operation="open" class="file" profile="who" name="/etc/nsswitch.conf" pid=78600 comm="who" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
2026-10-21T11:35:20.980069+00:00 linux kernel: audit: type=1400 audit(1792582520.978:228): apparmor="DENIED" operation="open" class="file" profile="who" name="/etc/passwd" pid=78600 comm="who" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
2026-10-21T11:35:20.980070+00:00 linux kernel: audit: type=1400 audit(1792582520.978:229): apparmor="DENIED" operation="open" class="file" profile="who" name="/etc/nsswitch.conf" pid=78600 comm="who" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
2026-10-21T11:35:20.980070+00:00 linux kernel: audit: type=1400 audit(1792582520.978:230): apparmor="DENIED" operation="open" class="file" profile="who" name="/etc/passwd" pid=78600 comm="who" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
2026-10-21T11:35:21.023143+00:00 linux systemd[1]: session-7.scope: Deactivated successfully.Follow a file as it grows: tail -f
tail -f prints the end of a file and then keeps watching it, printing each new line as it's added. You watch a log while you restart a service or test a website, and you see errors the moment they happen. Press Ctrl+C to stop.
Here a background loop adds a line every second, and timeout 4 stops tail after four seconds so the example ends by itself:
$ (for i in 1 2 3; do sleep 1; echo "08:0$((4+i)) request $i" >> app.log; done) & [1] 80657 $ timeout 4 tail -f app.log 08:00 start 08:01 ok 08:02 warning: disk 81% 08:03 ok 08:04 error: disk full 08:05 request 1 08:06 request 2 08:07 request 3 [1]+ Done ( for i in 1 2 3; do sleep 1; echo "08:0$((4+i)) request $i" >> app.log; done )
The & runs the loop in the background (lesson 17). [1] and a number is Bash telling you it started, and the Done message at the end is Bash telling you it finished. In between, tail printed the last lines of the file, then each new one as it arrived.
Logs get rotated: renamed, compressed and replaced by a fresh file, usually once a day or once a week. tail -f keeps watching the old renamed file and goes quiet. tail -F notices the new file and switches to it, so use -F for logs you'll watch for a long time.
Try it
- Open two terminals into your VM (run
multipass shell linuxin a second window). In one, runtail -f ~/read-lab/app.log. In the other, runecho "08:10 hello" >> ~/read-lab/app.log. What happens in the first one?
Answer
The line appears in the first terminal straight away. Press Ctrl+C there to stop following.
watch: run something again and again
watch runs a command every two seconds and shows the result full screen, so you see it change:
$ watch df -h / $ watch -n 1 -d ls -l ~/read-lab
-n 1 runs it every second, and -d highlights what changed since the last run. The top line shows the command and the time. Press Ctrl+C to stop. It's the easy way to keep an eye on disk space while a big download runs, or on a directory while a script writes to it.
Try it
- Watch the time update every second.
- Watch
~/read-labwith differences highlighted, and in a second terminal create a file there.
Answer
watch -n 1 datewatch -d ls -l ~/read-labin one terminal andtouch ~/read-lab/new.txtin the other. The new line lights up.
Common mistakes
- **
caton a huge file.** Your terminal scrolls for ages. PressCtrl+C, then uselessortail. - **Forgetting
q.**lessis waiting for you.qleaves it. - Missing log entries because of permissions. Some logs need
sudoeven in theadmgroup. IftailsaysPermission denied, addsudo. - **
tail -fgoing quiet.** If a log was rotated, you're watching the old file. Usetail -F. - **Mixing up
head -n -5andtail -n +5.** The first drops the last five lines, the second starts at line five.
Practice
- Print lines 41 to 50 of
numbers.txt. (Hint: take the first 50 lines, then the last 10 of those.|sends one command's output into the next, as lesson 8 explains.)
Answer
$ head -n 50 numbers.txt | tail -n 10
41
42
43
44
45
46
47
48
49
50- Print
/etc/os-releasewithout its first line.
Answer
$ tail -n +2 /etc/os-release
NAME="Ubuntu"
VERSION_ID="26.04"
VERSION="26.04.1 LTS (Resolute Raccoon)"
VERSION_CODENAME=resolute
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=resolute
LOGO=ubuntu-logo- Show the last 3 lines of
/var/log/dpkg.logwith line numbers counted from the start of the file. (Hint:cat -nfirst.)
Answer
$ cat -n /var/log/dpkg.log | tail -n 3
1947 2026-10-21 11:33:41 trigproc man-db:arm64 2.13.1-1build1 <none>
1948 2026-10-21 11:33:41 status half-configured man-db:arm64 2.13.1-1build1
1949 2026-10-21 11:33:41 status installed man-db:arm64 2.13.1-1build1- Make a file
wide.txtcontaininga, a tab,b, and a space at the end. Show where the tab and the space are.
Answer
$ printf "a\tb \n" > wide.txt $ cat -A wide.txt a^Ib $
- Print the newest entry in
app.logfirst.
Answer
$ tac app.log | head -n 1
08:07 request 3- In
less /var/log/syslog, jump to the end and search backwards for the last line containingsystemd.
Answer
less /var/log/syslog, then G to jump to the end, ?systemd and Enter to search backwards, and q to quit.
Cheat sheet
| Command | What it does |
|---|---|
cat file | Print a whole (small) file |
cat a b | Print a then b |
cat -n, cat -A | Number lines, show tabs and line ends |
tac file | Print it last line first |
less file | Page through it (/ search, G end, q quit) |
less -N, less -S | Line numbers, no wrapping |
head file, head -n 5 | First 10 lines, first 5 |
head -n -5 | All but the last 5 |
tail file, tail -n 5 | Last 10 lines, last 5 |
tail -n +5 | From line 5 to the end |
tail -f, tail -F | Follow new lines (-F survives log rotation) |
watch -n 1 -d cmd | Run cmd every second, highlight changes |
seq 1 100 | Print numbers, handy for practice files |
← Listing files: ls, file types and timestampsCreating, copying, moving and deleting files →