Why this matters
Most of what you do in a shell is make files, look inside them, and pull out the part you care about: the last lines of a log after an error, the header of a CSV, the size of a dump. Graphical editors choke on a 2 GB log file; tail reads its last ten lines instantly because it jumps straight to the end. This lesson gives you the everyday toolkit.
Making directories: mkdir
$ mkdir reports
$ mkdir reports/2026/october
mkdir: cannot create directory 'reports/2026/october': No such file or directory
$ mkdir -p reports/2026/october
$ mkdir -p reports/2026/october
$
Plain mkdir creates one level and fails if the parent is missing or the directory already exists. -p (parents) creates every missing directory along the path and does not complain if the target exists, which makes it safe to run twice: ideal in scripts. mkdir accepts several paths at once: mkdir -p src tests docs.
Making files: touch, echo and redirection
touch creates an empty file if the name does not exist, and otherwise just updates its timestamps:
$ touch a.txt b.txt
$ ls -l a.txt
-rw-r--r--. 1 learner learner 0 Nov 1 14:30 a.txt
$ touch -d '2020-01-01 00:00' a.txt
$ ls -l a.txt
-rw-r--r--. 1 learner learner 0 Jan 1 2020 a.txt
To create a file with content, redirect output into it:
$ echo "first line" > notes.txt
$ echo "second line" >> notes.txt
$ cat notes.txt
first line
second line
> creates or empties the file first; >> appends. Mixing them up is the most common way to lose a file's content, so read your line before pressing Enter.
Editing files: nano
For anything longer, use an editor. nano is the friendliest one installed here:
$ nano notes.txt
The bottom two lines of nano's screen list its commands; ^ means Ctrl. The ones you need: Ctrl-O then Enter to save ("write Out"), Ctrl-X to exit, Ctrl-K to cut a line, Ctrl-U to paste, Ctrl-W to search. (vi is also installed; if you open it by accident, type :q! and Enter to escape without saving.)
Viewing: cat, less
cat (concatenate) prints whole files, one after the other:
$ cat notes.txt poem.txt
That is fine for short files. For long ones, use the pager less, the same viewer man uses: Space and b to page, /text to search, G to go to the end, q to quit. less reads only what it needs to show, so it opens huge files instantly. less -N shows line numbers, and cat -n prints them too.
The beginning and the end: head, tail
$ head server.log
2026-10-31 12:00:01 INFO request 1 served in 40ms
... (10 lines by default)
$ head -n 3 server.log
$ tail -n 3 server.log
2026-10-31 12:03:18 INFO request 198 served in 129ms
2026-10-31 12:03:19 INFO request 199 served in 166ms
2026-10-31 12:03:20 INFO request 200 served in 203ms
head -n Nprints the first N lines,tail -n Nthe last N.tail -n +Nprints from line N to the end (skip a CSV header withtail -n +2).head -c N/tail -c Ncount bytes instead of lines.tail -f filekeeps the file open and prints new lines as they are appended: the standard way to watch a live log. Press Ctrl-C to stop.
To get line 100 alone, combine them: take the first 100, then the last of those.
$ head -n 100 server.log | tail -n 1
2026-10-31 12:01:40 INFO request 100 served in 103ms
Counting: wc
wc (word count) counts lines, words and bytes:
$ wc server.log
200 1600 10467 server.log
$ wc -l server.log
200 server.log
$ wc -l < server.log
200
With a file name argument, wc prints the name too; when it reads from its input (< file), it does not know any name, so it prints just the number. That trick is useful in scripts.
What happens underneath: how tail finds the end
A file is a sequence of bytes, and "lines" are just stretches of bytes ending in a newline character (byte 10, written \n). There is no index of lines. So how does tail find the last three lines of a 2 GB file without reading 2 GB?
file: [ .......... 2 GB of bytes .......... | line198\n line199\n line200\n ]
▲
lseek(fd, -8192, SEEK_END) ────┘ jump near the end, read backwards,
count newlines until it has 3 lines
tail asks the kernel to move its read position (lseek) to near the end and reads backwards in blocks, counting newlines. head simply reads from the start and stops. cat and wc, by contrast, must read every byte. Thinking about how much data a tool has to touch is a habit that pays off with big files.
Because a line is "bytes up to a \n", a file whose last line has no trailing newline confuses some tools: wc -l counts newline characters, so it reports one line fewer than you see. Editors like nano add the final newline for you; printf 'no newline' > f does not.
Common mistakes
>instead of>>. Overwrites the file. There is no undo.caton a binary file. Garbage on screen, possibly a scrambled terminal; typereset. Check withfilefirst.catwith no file argument. It waits for keyboard input; Ctrl-D ends it, Ctrl-C aborts.mkdirwithout-pin scripts. Fails on the second run withFile exists.- Forgetting that
tail -fnever ends by itself. It is waiting for more lines. Ctrl-C.
Recap
mkdir -pmakes whole paths and tolerates existing directories.touchcreates empty files or updates timestamps;>writes,>>appends.catprints,lesspages,nanoedits.head -n N,tail -n N,tail -n +N,tail -f; combine head and tail to pick a line.wc -l < filecounts lines and prints just the number.- A "line" is bytes up to a newline character; there is no line index in a file.
Going further: try tail -f in action. In the shell, run (for i in 1 2 3 4 5; do echo tick $i >> clock.log; sleep 1; done) & and then immediately tail -f clock.log. Press Ctrl-C when it stops ticking.