Nandishwar Singh

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

  1. Set up some files to read
  2. cat: print a whole file
  3. less: read big files
  4. head: the start of a file
  5. tail: the end of a file
  6. watch: run something again and again
  7. Common mistakes
  8. Practice
  9. 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 full

Give 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
linux

Two options help when you're looking closely at a 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 start

Don'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

  1. Print /etc/os-release with line numbers.
  2. Print app.log and numbers.txt together. 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-logo

For 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
KeyDoes
Space / bNext / previous page
Arrow keys, j / kOne line down / up
g / GStart / end of the file
/text, ?textSearch forward / backward
n / NNext / previous match
&textShow only the lines containing text (& alone shows all again)
-N then EnterTurn line numbers on or off
FFollow the file as it grows (Ctrl+C to stop following)
qQuit

less doesn't load the whole file before showing it, so it opens a multi-gigabyte log instantly. Two options are worth knowing:

Try it

  1. Open the GPL licence in less, search for warranty, and jump through the matches.
  2. In less app.log, show only the lines containing disk.
Answer
  1. less /usr/share/common-licenses/GPL-3, then /warranty Enter, then n for each next match, and q to leave.
  2. less app.log, then type &disk and 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

  1. Print the first 5 lines of /etc/passwd.
  2. Print everything in app.log except 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
100

With 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
100

ubuntu 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

  1. Open two terminals into your VM (run multipass shell linux in a second window). In one, run tail -f ~/read-lab/app.log. In the other, run echo "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

  1. Watch the time update every second.
  2. Watch ~/read-lab with differences highlighted, and in a second terminal create a file there.
Answer
  1. watch -n 1 date
  2. watch -d ls -l ~/read-lab in one terminal and touch ~/read-lab/new.txt in the other. The new line lights up.

Common mistakes

Practice

  1. 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
  1. Print /etc/os-release without 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
  1. Show the last 3 lines of /var/log/dpkg.log with line numbers counted from the start of the file. (Hint: cat -n first.)
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
  1. Make a file wide.txt containing a, 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 $
  1. Print the newest entry in app.log first.
Answer
$ tac app.log | head -n 1
08:07 request 3
  1. In less /var/log/syslog, jump to the end and search backwards for the last line containing systemd.
Answer

less /var/log/syslog, then G to jump to the end, ?systemd and Enter to search backwards, and q to quit.

Cheat sheet

CommandWhat it does
cat filePrint a whole (small) file
cat a bPrint a then b
cat -n, cat -ANumber lines, show tabs and line ends
tac filePrint it last line first
less filePage through it (/ search, G end, q quit)
less -N, less -SLine numbers, no wrapping
head file, head -n 5First 10 lines, first 5
head -n -5All but the last 5
tail file, tail -n 5Last 10 lines, last 5
tail -n +5From line 5 to the end
tail -f, tail -FFollow new lines (-F survives log rotation)
watch -n 1 -d cmdRun cmd every second, highlight changes
seq 1 100Print numbers, handy for practice files