๐ Lesson 4: Reading and Searching Text
cat, head, tail, wc and grep: look inside files, and find the lines that matter
Why text matters so much
Almost everything on a Linux system is controlled by a plain text file: settings, user lists, logs of what happened, even the description of the operating system. That is a big reason Linux is so scriptable and so easy to inspect. If you can read text and search it, you can find out nearly anything about a machine. This lesson gives you the five tools you will use for that every single day.
Everything below was run on a real Ubuntu 24.04, using files every Ubuntu has, so you can follow along exactly. Open your practice machine.
Show a file: cat
cat prints a whole file to the screen. The name comes from "concatenate", but think of it as "show me this file".
ubuntu@practice:~$ cat /etc/hostname practice ubuntu@practice:~$ cat /etc/os-release PRETTY_NAME="Ubuntu 24.04.5 LTS" NAME="Ubuntu" VERSION_ID="24.04" VERSION="24.04.5 LTS (Noble Numbat)" VERSION_CODENAME=noble 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=noble LOGO=ubuntu-logo
/etc/hostname is a one-line file with the machine's name (it said practice, the name we gave our container). /etc/os-release describes the operating system, and the first line says Ubuntu 24.04.5 LTS. This is the file the head -4 in lesson 1 read.
cat is perfect for small files. For a long one it floods the screen, and the part you wanted scrolls away. For that you have the next tools.
The start, the end and the size: head, tail and wc
ubuntu@practice:~$ head /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 games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin ubuntu@practice:~$ head -n 3 /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 ubuntu@practice:~$ tail -n 3 /etc/passwd _apt:x:42:65534::/nonexistent:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash ubuntu@practice:~$ wc -l /etc/passwd 19 /etc/passwd ubuntu@practice:~$ wc /etc/passwd 19 21 888 /etc/passwd
- head shows the first 10 lines of a file. head -n 3 shows the first 3. (The n is for "number of lines".)
- tail shows the last lines. tail -n 3 shows the last 3. In practice, tail is how people look at the newest entries of a log file.
- wc means "word count". wc -l counts only the lines: /etc/passwd has 19 of them. Plain wc gives three numbers: lines, words and bytes (here 19, 21 and 888).
A note on what you are looking at: /etc/passwd is the list of user accounts on the machine. Each line is one account, with fields separated by colons: the name, an x (the password is kept elsewhere, in a protected file), the user and group numbers, a description, the home folder and the shell. The first line is root, the administrator, and the last is ubuntu, the account you are using.
Reading a long file a screen at a time
For a really long file you normally use a pager called less, which shows one screen at a time and lets you scroll and search. In this bare-bones practice container it is not installed, and the shell says so:
ubuntu@practice:~$ less /etc/passwd bash: less: command not found
That is the expected result here, and it is a good example of an error that does not mean you did something wrong: the program simply is not on this machine. Lesson 6 shows you how to install software, and less will be one of the first things we install. On a normal Ubuntu (WSL, a virtual machine, a real computer) it is already there, and you use it like this, from the documentation, not run here: less FILE opens the file, the Space bar moves down a page, b moves back up, /word searches for a word, and q quits.
Find the lines that matter: grep
grep is the single most useful text tool you will learn. It reads a file and prints only the lines that contain what you are looking for. The name is old Unix shorthand for "search globally for a regular expression and print", and nobody expects you to remember that.
ubuntu@practice:~$ grep root /etc/passwd root:x:0:0:root:/root:/bin/bash ubuntu@practice:~$ grep -n root /etc/passwd 1:root:x:0:0:root:/root:/bin/bash ubuntu@practice:~$ grep ROOT /etc/passwd ubuntu@practice:~$ grep -i ROOT /etc/passwd root:x:0:0:root:/root:/bin/bash ubuntu@practice:~$ grep -c nologin /etc/passwd 16 ubuntu@practice:~$ grep -v nologin /etc/passwd root:x:0:0:root:/root:/bin/bash sync:x:4:65534:sync:/bin:/bin/sync ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
- grep root /etc/passwd printed the one line that contains the word root.
- grep -n adds line numbers: that line is line 1.
- grep ROOT /etc/passwd printed nothing. Searches are case sensitive, so ROOT is not root. No output simply means "no matching lines".
- grep -i makes the search ignore the case, and it found the line again.
- grep -c nologin counts the matching lines instead of showing them: 16 accounts use the nologin shell, which means they are not for people to log in with.
- grep -v nologin shows the opposite: the lines that do not contain the word. Three accounts are left: root and ubuntu, which have a real shell you can log in to, and sync, which has a special one-purpose program instead of a shell.
Search several files, and files you cannot read
ubuntu@practice:~$ grep -l root /etc/passwd /etc/group /etc/hostname /etc/passwd /etc/group ubuntu@practice:~$ grep -rl ubuntu /etc/apt /etc/apt/sources.list /etc/apt/trusted.gpg.d/ubuntu-keyring-2012-cdimage.gpg /etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg /etc/apt/sources.list.d/ubuntu.sources ubuntu@practice:~$ cat /etc/shadow cat: /etc/shadow: Permission denied ubuntu@practice:~$ ls -l /etc/shadow /etc/passwd -rw-r--r-- 1 root root 888 Sep 17 02:29 /etc/passwd -rw-r----- 1 root shadow 502 Sep 17 02:29 /etc/shadow
- Give grep more than one file and it can tell you which ones match. grep -l prints just the names of the files that contain the word, so /etc/hostname was left out.
- grep -r searches recursively: every file in a folder and everything below it. grep -rl ubuntu /etc/apt found the four files in there that mention Ubuntu.
- Then something instructive. cat /etc/shadow printed "Permission denied". That file stores password data, and only the administrator and the shadow group may read it, as the long listing shows: -rw-r-----. It is the same Permission denied you met in lesson 2, and lesson 5 explains exactly how to read and change those letters.
Find files by name: find
grep searches inside files. find searches for files by name, and by other properties:
ubuntu@practice:~$ find /usr/share/common-licenses -name "GPL*" /usr/share/common-licenses/GPL-2 /usr/share/common-licenses/GPL-1 /usr/share/common-licenses/GPL-3 /usr/share/common-licenses/GPL ubuntu@practice:~$ find /usr/share/common-licenses -name "*2*" /usr/share/common-licenses/GPL-2 /usr/share/common-licenses/GFDL-1.2 /usr/share/common-licenses/Apache-2.0 /usr/share/common-licenses/LGPL-2 /usr/share/common-licenses/LGPL-2.1 /usr/share/common-licenses/MPL-2.0 ubuntu@practice:~$ find /etc -name "host*" /etc/host.conf /etc/hostname /etc/hosts ubuntu@practice:~$ find /etc/security -name "*.conf" -type f /etc/security/faillock.conf /etc/security/group.conf /etc/security/namespace.conf /etc/security/pam_env.conf /etc/security/pwhistory.conf /etc/security/time.conf /etc/security/limits.conf /etc/security/sepermit.conf /etc/security/access.conf
- find /usr/share/common-licenses -name "GPL*" looked in that folder and everything below it, for names starting with GPL. The quotes stop the shell from expanding the * itself, so find gets the pattern. It is a good habit to always quote patterns for find.
- -name "*2*" found any name containing a 2.
- find /etc -name "host*" searched the whole configuration folder.
- -type f limits the results to files, and find /etc/security -name "*.conf" -type f lists the settings files in that folder.
Did it find anything? The exit status
Every command leaves behind a small number saying whether it succeeded, called its exit status. Zero means success. grep uses a non-zero number to mean "no matches". You can see it with echo $?, which prints the status of the last command:
ubuntu@practice:~$ grep root /etc/passwd root:x:0:0:root:/root:/bin/bash ubuntu@practice:~$ echo $? 0 ubuntu@practice:~$ grep zzz /etc/passwd ubuntu@practice:~$ echo $? 1
After a successful search the status is 0. After a search with no matches it is 1. You will not need this today, but it is the reason scripts can ask "did grep find anything?", and you will use it in lesson 11.
Because they kept typing <span class="command">cat</span> to find the cat, and all they got was a file.
๐งช Try it yourself
1. Show the first 5 lines of /etc/passwd. 2. How many lines does /etc/os-release have? (Use one command.) 3. Which line of /etc/os-release contains the word Noble? Show the line number too. 4. Find every file under /usr/share/common-licenses whose name contains Apache. 5. List the accounts in /etc/passwd that are not nologin accounts.
The cheat sheet
- cat FILE show the whole file. head FILE the first lines. tail FILE the last lines. -n 3 picks how many.
- wc -l FILE count the lines.
- grep WORD FILE show the matching lines. -n line numbers, -i ignore case, -c count, -v the lines that do not match, -l just the file names, -r search a whole folder.
- find WHERE -name "PATTERN" find files by name. -type f for files only.
- echo $? shows whether the last command succeeded (0) or not.
- less FILE for long files (installed in lesson 6): Space for the next page, /word to search, q to quit.
← Lesson 3 · ↑ All lessons · Lesson 5: Users, Permissions and sudo →