Module 3 — Reading and Editing Files
Updated 2 September 2026
Nearly everything on a Linux server — configuration, logs, code, state — is plain text. This module teaches you to read it at any size, edit it in the two editors you will actually meet, understand what a file is underneath its name, and pack files for transport. Half of all real troubleshooting is done with the tools on this page.
Legend used throughout: 🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)
You need Module 1 — What Linux Is (commands, errors, man) and Module 2 — The Filesystem (paths, cd/ls, mkdir/cp/mv/rm, your home sandbox). Tools: the same Ubuntu 24.04 environment; nano, vim, tar, and gzip ship with it. This module's sandbox is ~/reading — create it now: mkdir ~/reading.
Part A — Reading files at any size
Which tool reads what? One decision tree for the whole Part:
Diagram source
flowchart TD
A["Need to read a file"] --> B{"Roughly how big?"}
B -->|"a screenful"| C["cat<br>dump it all"]
B -->|"big"| D{"Which part?"}
D -->|"browse / search"| E["less<br>page both ways"]
D -->|"just the start"| F["head"]
D -->|"just the end"| G["tail"]
D -->|"the end, as it grows"| H["tail -f<br>live follow"]
A --> I{"Not sure it's text?"}
I -->|"check first"| J["file<br>identify the type"]A1. cat and file — dump it, but check first
🧠 cat file prints a file's entire contents to the terminal, top to bottom, no pauses. (The name means concatenate — given several files it prints them joined, which is its original job.) Perfect for small files; wrong for big ones, and actively hazardous for non-text ones.
That second case is why file somefile exists: it inspects a file's content (not its name) and tells you what the content actually is — text, program, archive, image. Linux does not trust filename extensions; neither should you.
cat is an assistant who opens a letter and reads the whole thing aloud, start to finish, no pauses, no questions. file is the habit of checking the envelope first — this one's a letter, that one's a parcel, that one ticks.
Where the analogy stops working. A human reader adapts to the listener; cat reads at machine speed. Ask it to read a 2 GB log and it will, flooding your terminal for minutes — the tool has no judgement, which is exactly why the choice of tool is yours.
🧪 Exercise A1.1 — cat the small, identify the strange
cat /etc/hostname # a one-line file: this machine's name
cat /etc/os-release # Module 2's friend — a proper small config file
file /etc/os-release # what does content-inspection say it is?
file /usr/bin/man # and a real program?
file /bin # and this one — surprise pending✅ Expected result — click to reveal
myhost
PRETTY_NAME="Ubuntu 24.04.4 LTS"
NAME="Ubuntu"
...
/etc/os-release: symbolic link to ../usr/lib/os-release
/usr/bin/man: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, ...
/bin: symbolic link to usr/binWhat to read out of it:
- The two cats behaved like Module 2 promised: small text, dumped whole.
- Surprise number one: on stock Ubuntu, /etc/os-release is itself a symbolic link — a name pointing at another name (Part C's whole subject), and file reports the link rather than what it leads to. Add -L to follow it: file -L /etc/os-release says ASCII text — safe for cat. On /usr/bin/man, file says ELF ... executable: ELF is Linux's program format; this is the thing that would garble your terminal.
- /bin: symbolic link to usr/bin — Module 2's usr-merge, now visible as a link: a name that points at another name. Part C is entirely about these.
A2. less — the pager for everything bigger
🧠 less file opens a file in the same full-screen reader you met inside man in Module 1 — because man was using less all along. Same keys: Space page down, b page up, arrows scroll, /word search forward, n next match, N previous match, g jump to top, G jump to end, q quit. Nothing you view is ever modified — less is read-only by design, which makes it the safe default for looking at anything on a production machine.
less is proper book-reading: flip forward, flip back, scan for a phrase, jump to the index. cat was having the whole book read at you.
Where the analogy stops working. A book must exist in full before you read page one. less reads lazily — it opens a 10 GB file instantly because it loads only what you are looking at. This is why less on an enormous log is fine while cat on the same file is a mistake.
🧪 Exercise A2.1 — Browse a genuinely big file
wc -l /etc/services # wc -l counts lines: how big is this file? (wc's full story: B3)
less /etc/services # now open it. Inside: Space, b, then /ssh and n, then G, then q✅ Expected result — click to reveal
361 /etc/servicesthen a full-screen view beginning with comment lines (# Network services, Internet style) and column after column of service names and ports.
What to read out of it:
- 361 lines (yours may differ — the package that ships this file trims or extends it over time) — roughly a dozen screenfuls: exactly the size where cat becomes scrolling noise and less becomes comfortable.
- /ssh then n hops you between mentions of ssh — port 22 appears in a line like ssh 22/tcp. You are practicing on the file that maps service names to port numbers, a file Module 13 returns to.
- G jumps to the end; q brings your prompt back. If a less session ever feels stuck: it is waiting for q, the same first-day lesson as man.
A3. head, tail — and tail -f, the log-watcher
🧠 head file prints the first 10 lines; tail file the last 10; -n 3 (or just -3) adjusts the count. Ends of files matter more than middles in operations: the top identifies a file (headers, comments), and the bottom of a log is the present moment.
Which brings us to the single most-used flag in DevOps: tail -f file (follow) prints the end, then stays running, printing each new line the instant something appends one. It is how you watch a service live. It never exits on its own — Ctrl+C (hold Ctrl, press c) asks it to stop; that key combination interrupts the current foreground program, a mechanism Module 8 explains properly.
head reads a newspaper's front-page headlines; tail flips straight to the back page where the latest scores went to print. tail -f is the stock ticker: a screen bolted to the wall, updating itself as events happen.
Where the analogy stops working. A ticker is curated and delayed. tail -f shows raw bytes the moment they land in the file — unfiltered, unformatted, and exactly what the program wrote, typos and stack traces included. That rawness is the value.
🧪 Exercise A3.1 — Heads and tails
head -3 /etc/services
tail -3 /etc/services✅ Expected result — click to reveal
# Network services, Internet style
#
# Updated from https://www.iana.org/assignments/service-names-port-numbers ...then the file's final three lines — on current Ubuntu's trimmed file: a fidonet entry, a blank line, and a # Local services comment.
What to read out of it:
- The head is all comments — files introduce themselves at the top, which is why head unknownfile is the polite first question to ask any file.
- Both commands returned instantly and printed exactly three lines — and tail's three include a blank line and a trailing comment, because tools count lines, not meaning. For "just show me the edge", they beat both cat and less.
🧪 Exercise A3.2 — Watch a file grow, live (needs two terminals)
# TERMINAL 1: create a file and follow it
cd ~/reading
touch live.log
tail -f live.log # it prints nothing yet — the file is empty. It is NOT stuck.
# TERMINAL 2: append lines (>> appends a line to a file — a one-line loan from Module 5)
echo "first event" >> ~/reading/live.log
echo "second event" >> ~/reading/live.log
# TERMINAL 1: watch them appear the instant you press Enter in terminal 2.
# When done: Ctrl+C in terminal 1 to stop following.✅ Expected result — click to reveal
In terminal 1, nothing at first; then, as you run each echo in terminal 2:
first event
second eventthen ^C and your prompt back when you interrupt it.
What to read out of it:
- The delay between pressing Enter in one terminal and seeing the line in the other is imperceptible — this is the live wire you will hold against every misbehaving service: tail -f its log while you poke it from another terminal.
- The ^C echoed on screen is the terminal showing the interrupt you typed. tail exits; the file, of course, keeps its contents.
- The two-terminal habit itself is the meta-lesson: one window acts, one window observes. Professionals debug in pairs of terminals.
Part A — Interview questions
🎯 "What is the difference between cat, more and less?" — asked verbatim in GeeksforGeeks' Linux Interview Questions (70+ questions, updated July 2026)
cat prints a file whole, no paging — right for small files and for joining files, wrong for big ones. more is the historical pager — traditionally forward-only and quitting at end of file (modern versions have grown backward paging, but less remains the full-featured one). less is its modern replacement — pages both directions, searches with /, jumps with g/G, reads lazily so huge files open instantly, and never modifies anything. The name is the joke: "less is more" — less does more than more.
The details that separate candidates: lazy reading as the reason less handles gigabyte logs where cat floods the terminal; knowing man itself uses less (so the keys are already in your fingers); and the operational habit — less is the safe default on production boxes precisely because it is read-only.
🎯 "What do head and tail do?" — asked verbatim in GeeksforGeeks' Linux Interview Questions (70+ questions, updated July 2026)
First and last 10 lines respectively, -n N to adjust. The flag that matters operationally is tail -f: follow the file, printing new lines as they are appended — the standard way to watch a log live during a deploy or an incident. tail -n 100 file | less is the common combo for "recent history, browsable" (pipes formally arrive in Module 5).
The details that separate candidates: volunteering tail -f unprompted with a concrete use ("watch the app log while I restart the service"); knowing tail -F exists for logs that get rotated — it re-opens the file when the name is replaced, which plain -f does not survive (log rotation is a Module 14 topic).
Part B — Editing: nano, vim, and proving what changed
B1. nano — the editor that explains itself
🧠 nano file opens (or creates) a file for editing in a full-screen editor that keeps its commands printed along the bottom. The notation there: ^X means Ctrl+X (the caret stands for Ctrl). The three you need: ^O write out (save — it asks you to confirm the filename; Enter accepts), ^X exit (offering to save if you have unsaved changes), ^W search (where is). Typing just… types. That absence of surprise is nano's entire philosophy, and why it is the right first editor.
nano is a microwave with the three buttons you need labelled on the door. Nobody reads a manual to use it, and nobody gets trapped inside.
Where the analogy stops working. The ^ notation is the one unlabelled assumption — a beginner who presses the caret key ^ followed by X gets a caret and an X in their file. Once you know ^ means Ctrl, the front panel really is the whole manual.
🧪 Exercise B1.1 — Create a real config file
cd ~/reading
nano app.conf
# Inside nano, type these three lines exactly:
# server_name=web-01
# port=8080
# log_level=info
# Then: Ctrl+O, Enter to save — then Ctrl+X to exit.
cat app.conf # verify from outside✅ Expected result — click to reveal
server_name=web-01
port=8080
log_level=infoWhat to read out of it:
- The verification habit is the lesson: after any edit, read the file back with cat (or less). The editor showed you what it would save; cat shows what is actually on disk. Those differ exactly when something went wrong.
- You just hand-built the kind of key=value file that fills /etc. From here on, "edit the config" is a thing you can literally do.
B2. vim — survival level, on purpose
🧠 vim is the other editor — powerful, ancient, and modal, and you must be able to survive it because on minimal servers it is often what exists. Modal means keys change meaning by mode:
- Normal mode (where vim starts): letters are commands — typing does not insert text.
- Insert mode: letters are text. Enter it by pressing i; leave it with Esc.
- Command-line mode: prompted by : from normal mode — where saving and quitting live: :w write, :q quit, :wq both, and the survival move of the decade, :q! — quit discarding changes, for when the buffer is in a state you do not understand.
Survival loop, complete: vim file → i → type → Esc → :wq → Enter. That is enough vim for every emergency; mastery is optional, survival is not.
A car's pedals mean different things in drive and in park. vim's keyboard means different things in normal and insert mode. Experienced drivers switch modes without thought; learners stall in car parks.
Where the analogy stops working. Cars announce their gear on the dashboard, brightly. vim's mode indicator is a small -- INSERT -- at the bottom that beginners never look at — and pressing the wrong "pedal" in the wrong mode does not stall the car, it does something, silently, to your file. When in doubt: Esc, then :q!, then start again.
🧪 Exercise B2.1 — The survival loop, with a safety net
cd ~/reading
cp app.conf app.conf.bak # Module 2's cp as a seatbelt: back up BEFORE editing
vim app.conf
# Inside vim:
# press i (-- INSERT -- appears at the bottom)
# change port=8080 to port=9090, and log_level=info to log_level=debug
# add a new line: max_conn=200
# press Esc, then type :wq and press Enter
cat app.conf✅ Expected result — click to reveal
server_name=web-01
port=9090
log_level=debug
max_conn=200What to read out of it:
- If your file looks different — stray letters, missing lines — a command ran while you thought you were typing. No shame: cp app.conf.bak app.conf restores the original (the seatbelt paying out), and you try again.
- The .bak copy before editing is not beginner training wheels; it is exactly what professionals do before touching configs on machines that matter. You will see this habit again, formalized, in every later module that edits system files.
B3. wc and diff — measuring and proving the change
🧠 wc file counts lines, words, and bytes (in that order); wc -l lines only — the everyday one. diff old new compares two files line by line and prints only what differs — and prints nothing at all when they are identical, the strongest form of Module 1's silence-is-success.
Reading diff's dialect: 2,3c2,4 means lines 2–3 of the first file changed into lines 2–4 of the second; < lines are from the first file, > lines from the second. (a marks additions, d deletions.)
diff is the lawyer who lays the old and new draft side by side and flags only the clauses that changed — nobody re-reads the whole contract. The </> markers are "theirs" and "ours" in the margin.
Where the analogy stops working. A lawyer compares meaning. diff compares lines, literally — re-indent a paragraph, or change one space, and diff reports change where a human sees none. Whitespace is content to diff, a fact that resurfaces in code review for the rest of your career.
🧪 Exercise B3.1 — Prove exactly what your vim session did
cd ~/reading
wc app.conf.bak app.conf # size before vs after, in one command
diff app.conf.bak app.conf # the changes, and nothing but the changes✅ Expected result — click to reveal
3 3 44 app.conf.bak
4 4 58 app.conf
7 7 102 total
2,3c2,4
< port=8080
< log_level=info
---
> port=9090
> log_level=debug
> max_conn=200What to read out of it:
- wc's three columns per file: lines, words, bytes — 3 lines grew to 4. Given several files, it adds a total row unasked. (Byte counts may differ by a character or two from the sample if your typing differed — that is wc doing its job.)
- The diff block, decoded: lines 2–3 of the backup became lines 2–4 of the new file; < shows what they were, > what they are now. Your vim edit, reconstructed perfectly from the outside.
- This pair — backup, edit, diff — is the complete safe-change ritual. In Module 10 it becomes scriptable; in interviews, describing this ritual unprompted reads as experience.
Part B — Interview questions
🎯 "Name different types of modes used in VI editor." — asked verbatim in InterviewBit's Linux Interview Questions (2025)
Three: normal/command mode — vim's start state, where keys are commands (j down, x delete char, dd delete line); insert mode — entered with i (or a, o), where typing inserts text, left with Esc; and command-line (ex) mode — entered with : from normal mode, for file-level operations: :w save, :q quit, :wq both, :q! discard and quit. Everything confusing about vi is downstream of one fact: it starts in the mode where typing is not typing.
The details that separate candidates: naming the transitions (i, Esc, :) rather than just the modes; citing :q! as the universal escape hatch; and one fluency marker — knowing why modality exists at all (hands never leave the letter keys; commands compose, like 3dd for "delete three lines").
🎯 "How can you compare two text files and identify the differences between them?" — asked verbatim in Adaface's 96 Linux Commands interview questions (September 2024)
diff file1 file2 — output shows only changed regions: < lines from the first file, > from the second, with c/a/d hunks for changed/added/deleted. Identical files produce no output. diff -u gives the unified format (the one git and code review use, with -/+ lines and context); diff -r compares whole directory trees.
The details that separate candidates: knowing diff signals "files differ" through its exit status as well as its output — which is how scripts branch on it (Module 5 makes exit codes precise); and reflexively reaching for -u because unified diffs are the shared dialect of modern tooling.
Part C — What a file really is
C1. Inodes — the file behind the filename
Goes deeper: the theory module for this whole Part is Module 03 — File Descriptors, Inodes & Filesystems in the Operating Systems track — read it after this module.
🧠 Time to open the hood. On disk, a file is two separate things:
- The inode: a numbered record holding the file's metadata — size, owner, permissions, timestamps, and where on disk the actual data blocks live. One inode per file, identified by its inode number.
- A directory entry: a name → inode-number pairing, stored in the directory, not in the file. Module 2 told you a directory is a small list of name-tags; now you know what the tags point at.
ls -i shows inode numbers; stat file prints an inode's full contents in human form. This split is not trivia — it is the mechanism behind Module 2's instant rename (only the entry changed), and behind both kinds of link below.
In an old library, the card in the catalogue holds everything about a book — shelf location, size, acquisition date — and the drawers of cards are organized by title. The card is the inode; the title-to-card mapping in the drawer is the directory entry; the book on the shelf is the data blocks.
Where the analogy stops working. A library writes the title on the card too. The inode genuinely does not know its own name — and one card can be filed under several titles simultaneously, which in a library would be a mistake and in Linux is a feature (next section).
🧪 Exercise C1.1 — Meet an inode
cd ~/reading
ls -i app.conf # the inode number behind the name
stat app.conf # the inode's full record✅ Expected result — click to reveal
1171819 app.conf
File: app.conf
Size: 58 Blocks: 8 IO Block: 4096 regular file
Device: 254,0 Inode: 1171819 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2026-09-02 15:31:15.183274973 +0000
Modify: 2026-09-02 15:31:15.181410231 +0000
Change: 2026-09-02 15:31:15.181410231 +0000
Birth: 2026-09-02 15:31:15.171855317 +0000What to read out of it (numbers, owner, and dates will be your machine's own):
- The inode number (1171819 here) is the file's true identity on this filesystem. Names come and go; this number is the file.
- Links: 1 — the same count ls -l showed in its second column back in Module 2. It counts how many directory entries point at this inode. One, so far. C2 raises it.
- The Access: line with 0644/-rw-r--r-- is permissions — Module 4 decodes it. Three timestamps plus Birth: C4's subject.
- stat is ls -l's verbose sibling: everything the filesystem knows about one file, on one screen.
C2. Hard links — one file, several names
🧠 ln existing newname creates a hard link: a second directory entry pointing at the same inode. Not a copy — there is one file, one set of data blocks, now reachable by two names. Edit through either name and both "see" the change, because there is no both — there is one file. Deleting a name just removes an entry and decrements the link count; the file's data is freed only when the count reaches zero (and no program holds it open).
Limits worth knowing: hard links cannot span filesystems (inode numbers are per-filesystem), and you cannot hard-link directories.
The library files one physical book under "Cooking, Italian" and also under "Pasta, history of". Two cards… no — two titles on the same card's trail: request either title, receive the same book. The book leaves the collection only when the last title referencing it is withdrawn.
Where the analogy stops working. Library patrons would call one title the real one. Linux refuses the concept: link count is just a reference counter, and every name is a first-class citizen. Also, the two titles must be in the same library — links cannot point into another building (another filesystem).
🧪 Exercise C2.1 — Two names, one inode
cd ~/reading
ln app.conf hard.conf # second name for the same inode
ls -li app.conf hard.conf # -l and -i together: watch two columns✅ Expected result — click to reveal
1171819 -rw-r--r-- 2 root root 58 Sep 2 15:31 app.conf
1171819 -rw-r--r-- 2 root root 58 Sep 2 15:31 hard.confWhat to read out of it:
- Column 1: identical inode numbers — the proof there is one file. Everything else matches because everything else is the inode's metadata, displayed twice.
- Column 3 (after permissions): the link count now reads 2 on both lines — Module 2's mystery column, solved. Edit hard.conf in nano, then cat app.conf: same content, necessarily.
- Try rm app.conf, then cat hard.conf — the content survives; the count drops back to 1. Then restore for the next section: mv hard.conf app.conf (a rename — the inode never noticed any of this).
C3. Symbolic links — a name that points at a name
🧠 ln -s target linkname creates a symbolic link (symlink, soft link): a tiny special file whose entire content is a path, as text. Open the symlink and the kernel transparently follows the path and opens the target instead. Because the stored thing is a path — not an inode — symlinks can cross filesystems, can point at directories, and can point at things that do not exist. That last property is both their flexibility and their failure mode: delete the target and the symlink remains, aimed at nothing — dangling. ls -l displays every symlink as name -> target, which makes them self-documenting.
A symlink is a mail-forwarding card at an old address: "now residing at 14 Elm St." Mail sent to the old address gets delivered to the new one, automatically. /bin -> usr/bin from A1.1 is such a card, filed decades after the move.
Where the analogy stops working. The post office notices when the destination stops existing and returns your mail with an explanation. A symlink verifies nothing, ever — the forwarding card happily points at a demolished house, and you discover it only when your delivery fails, with an error that names the symlink rather than the missing target. That asymmetry confuses everyone once.
🧪 Exercise C3.1 — A link, and then a betrayal (second half fails on purpose)
cd ~/reading
ln -s app.conf soft.conf # a symlink to the config
ls -l soft.conf # see the arrow
cat soft.conf # reads THROUGH the link
rm app.conf # now delete the TARGET
cat soft.conf # the symlink is still there... but
ls -l soft.conf # and yet it looks fine in ls!
file soft.conf # file tells the truth✅ Expected result — an error, on purpose — click to reveal
lrwxrwxrwx 1 root root 8 Sep 2 15:31 soft.conf -> app.conf
server_name=web-01
port=9090
...
cat: soft.conf: No such file or directory
lrwxrwxrwx 1 root root 8 Sep 2 15:31 soft.conf -> app.conf
soft.conf: broken symbolic link to app.confWhat to read out of it:
- Before the deletion: leading l in the mode string, and -> app.conf — a symlink announces itself. cat through it read the target's content seamlessly.
- After deleting the target, the error is the confusing part, on purpose: cat names soft.conf — a file that visibly exists — as "No such file or directory". What is missing is the destination. When an existing path throws this error, think symlink, run ls -l, and file confirms: broken symbolic link.
- Note what did not happen: rm of the target gave no warning that links pointed at it. Nothing tracks symlinks pointing at a file. Restore the sandbox: cp app.conf.bak app.conf (the seatbelt again).
C4. The three timestamps
🧠 Every inode carries three timestamps (stat showed them): mtime (Modify) — when the file's content last changed; this is the one ls -l prints and the one everybody means by "modified". atime (Access) — when content was last read; often updated lazily for performance, so treat it as approximate. ctime (Change) — when the inode last changed: content edits, but also renames, permission changes, link-count changes. stat shows all three; many filesystems add Birth (creation) as a bonus. And Module 2's touch now makes full sense: its real job is setting mtime/atime to now — creating empty files is the side effect.
A well-run records office stamps a folder three ways: "content revised", "last consulted", "folder relabelled/moved". Different questions, different stamps — you audit content changes with the first and custody changes with the third.
Where the analogy stops working. An office clerk can forge any stamp. In Linux, mtime and atime can be set at will (touch -d), but ctime cannot be set by anyone, root included — the kernel stamps it itself. Which is exactly why forensics and sync tools trust ctime when mtime looks suspicious.
🧪 Exercise C4.1 — Make the stamps move independently
cd ~/reading
stat app.conf # note Modify and Change
mv app.conf application.conf # rename: content untouched
stat application.conf # which stamps moved?✅ Expected result — click to reveal
Two stat blocks; in the second, Modify: is unchanged while Change: has jumped to just now.
What to read out of it:
- The rename edited the directory and the inode's bookkeeping — not the content. mtime (content) stayed put; ctime (inode) advanced. This is Module 2's Ticket 4 evidence made precise: "same mtime" was the proof the 40 GB rename never touched data.
- Say the three aloud until fluent: mtime — content; atime — read; ctime — inode bookkeeping, untouchable. Interviewers reliably probe the mtime/ctime distinction.
Part C — Interview questions
🎯 "What is an inode?" — asked verbatim in GeeksforGeeks' Linux Interview Questions (70+ questions, updated July 2026)
The on-disk record that is the file: a numbered structure holding metadata — size, owner, permissions, the three timestamps, link count, and the locations of the data blocks. Everything except the name and the content itself. Names are directory entries mapping name → inode number, stored in directories. One inode may be reached by many names (hard links), and a file survives until its link count is zero and no process holds it open.
The details that separate candidates: "everything except the name" — the phrase that shows real understanding; the deletion rule (count zero + not open), which explains the classic "deleted a huge log but disk didn't shrink" incident; and knowing inodes are a per-filesystem resource that can run out while disk space remains (Module 12 stages that outage).
🎯 "What is the difference between a hard link and a soft link?" — asked verbatim in GeeksforGeeks' Linux Interview Questions (70+ questions, updated July 2026); Adaface (2024) words it "Describe how to create a symbolic link and a hard link, and explain the difference between them."
Create: ln target name (hard), ln -s target name (soft). A hard link is another directory entry for the same inode — equal in every way to the first name, same-filesystem only, files only, and the data survives until the last name goes. A soft link is a separate tiny file containing a path — it can cross filesystems, point at directories, and outlive its target, in which case it dangles and reads fail with No such file or directory despite the link existing. ls -l betrays soft links (l + arrow); hard links betray themselves only via matching inode numbers (ls -i) and a link count above 1.
The details that separate candidates: the mechanism framing (entry-to-inode vs file-containing-path) from which every difference derives — candidates who recite five memorized differences interview worse than those who derive them; plus one production use each: hard links in backup deduplication, symlinks as the current -> release pointer.
Part D — Packing files: tar, gzip, and unpacking safely
D1. tar — many files, one archive
🧠 tar (from tape archive — the tapes are gone, the name stayed) bundles a directory tree into one file and back. The three operations, each a flag: -c create, -t list, -x extract. Always paired with -f archivename (file — which archive), and almost always with -z (run the archive through gzip compression, next section). The spellings to memorize as words:
- tar -czf name.tar.gz directory/ — pack and compress ("compress ze files")
- tar -tzf name.tar.gz — list contents without extracting ("tell me")
- tar -xzf name.tar.gz — unpack ("extract ze files")
tar packs a whole shelf into one labelled box, preserving the arrangement, and tapes the packing list on top. Shipping one box beats shipping forty loose items — which is the actual point: one file transfers, stores, and checksums more reliably than a thousand.
Where the analogy stops working. A physical packing list can lie about the contents. tar -t reads the actual box, every time — and unlike movers, tar reproduces the original arrangement exactly, timestamps and (with the right flags) ownership included.
🧪 Exercise D1.1 — Pack, list, extract
cd ~/reading
mkdir -p project/docs
echo "hello" > project/readme.txt # (>> appends, > creates/overwrites — Module 5 owns these)
echo "some data" > project/docs/data.txt
tar -czf project.tar.gz project # pack the whole directory
ls -lh project.tar.gz
tar -tzf project.tar.gz # list WITHOUT unpacking
mkdir extract
tar -xzf project.tar.gz -C extract # -C: extract INTO that directory
ls extract/project✅ Expected result — click to reveal
-rw-r--r-- 1 zaeem zaeem 207 Sep 2 15:31 project.tar.gz
project/
project/readme.txt
project/docs/
project/docs/data.txt
docs readme.txtWhat to read out of it:
- The listing (-tzf) shows every path the archive contains, all starting with project/ — a well-mannered archive: extracting creates one directory, nothing else. Checking this before extracting is D3's whole lesson.
- -C extract dropped the tree inside extract/ — the final ls proves the structure travelled intact.
- Flag order gotcha worth knowing early: -f must be immediately followed by the archive name — tar -czf project.tar.gz works; tar -cfz project.tar.gz creates a confusing file named z.
D2. gzip — why .tar.gz is two tools
🧠 gzip compresses a single file (file → file.gz, original replaced; -k keeps it; gunzip reverses). That single-file limit explains the double extension you see everywhere: tar joins many files into one, gzip shrinks that one — .tar.gz is the fingerprint of the pair working in sequence. The -z flag you used in D1 just runs gzip inside tar in one step. Compression works by finding redundancy — repeated patterns get encoded once. Text compresses superbly (logs often 10:1); already-compressed data (JPEGs, videos, other .gz files) has no redundancy left.
gzip is a vacuum sealer: sucks the air (repetition) out of a duvet and it shrinks fourfold. tar folded all your clothes into one bag first; the sealer shrank the bag — two appliances, one shipment.
Where the analogy stops working. Every duvet has air. Not every file has redundancy — vacuum-sealing a brick (a JPEG) changes nothing, and double-sealing adds plastic while removing no air. The bag's size tells you about the contents: logs that stop compressing well have usually started containing compressed or binary junk.
🧪 Exercise D2.1 — Watch redundancy vanish, then fail to vanish twice
cd ~/reading
ls -l project.tar.gz
gzip -kf project.tar.gz # -k keep original; -f force — gzip normally REFUSES files already ending .gz (itself a hint)
ls -l project.tar.gz project.tar.gz.gz✅ Expected result — click to reveal
-rw-r--r-- 1 zaeem zaeem 207 Sep 2 15:31 project.tar.gz
-rw-r--r-- 1 zaeem zaeem 207 Sep 2 15:31 project.tar.gz
-rw-r--r-- 1 zaeem zaeem 242 Sep 2 15:35 project.tar.gz.gzWhat to read out of it:
- The double-compressed file is larger (242 vs 207 bytes here — exact sizes vary): gzip found no redundancy and added its own header. The counter-intuition, demonstrated in one command — and note gzip had to be forced into it: without -f it refuses anything already named .gz, the tool itself knowing the operation is pointless.
- Clean up the evidence: rm project.tar.gz.gz. And file the naming rule away: one .gz per file, ever.
D3. Extracting safely — list first, aim second
🧠 Two extraction accidents account for nearly all tar grief. First, the tarbomb: an archive whose entries do not share a top directory — extract it and forty files scatter directly into your current directory, intermixed with what was already there. Second, silent overwrite: extraction replaces existing files with archive contents, no questions asked. Both are neutralized by one ritual: tar -tzf to list before extracting, then extract with -C into a fresh directory when in doubt.
Movers who suspect a box was packed by an amateur open it in an empty room, not over the living-room floor. If forty loose items burst out, they burst out somewhere containable.
Where the analogy stops working. Physical unpacking is visible as it happens — you stop when things look wrong. tar extracts at machine speed and overwrites in place silently; by the time output scrolls, collisions already happened. The empty room must be chosen before, not during.
🧪 Exercise D3.1 — The missing archive (fails on purpose)
cd ~/reading
tar -xzf missing.tar.gz # an archive that does not exist✅ Expected result — an error, on purpose — click to reveal
tar (child): missing.tar.gz: Cannot open: No such file or directory
tar (child): Error is not recoverable: exiting now
tar: Child returned status 2
tar: Error is not recoverable: exiting nowWhat to read out of it:
- Four lines for one missing file — tar is verbose because it is actually two workers: tar itself and a helper process it spawns to run the gzip stage (tar (child)). The child hit the missing file, told the parent, both exited. Layered error messages like this are normal in Linux; read the first line for the cause, the rest is the chain of consequences.
- status 2 previews Module 5: programs report failure numerically, and 2 is tar's "fatal" code. Automation reads that number, not the prose.
Part D — Interview questions
🎯 "Which command would you use to create a compressed archive of a directory named my_directory into a file named my_archive.tar.gz?" — asked verbatim in Adaface's 96 Linux Commands interview questions (September 2024)
tar -czf my_archive.tar.gz my_directory — create, gzip-compress, into the file named right after -f, packing the named directory recursively. Verify afterwards with tar -tzf my_archive.tar.gz.
The details that separate candidates: explaining the flags as words (create / zip / file) instead of reciting a memorized string; the -f-must-precede-the-filename gotcha; and mentioning the verification list — interviewers notice candidates whose commands come with a built-in check.
🎯 "How can you compress a file in Linux, and what is the common extension for compressed files?" — asked verbatim in Adaface's 96 Linux Commands interview questions (September 2024)
gzip file produces file.gz, replacing the original (-k keeps it; gunzip reverses). .gz is the everyday extension; .tar.gz (or .tgz) marks the tar+gzip pair for directory trees. The wider family: bzip2/.bz2 and xz/.xz trade more CPU time for smaller output; zstd/.zst is the modern speed-versus-ratio favourite; zip bundles and compresses in one cross-platform format Windows opens natively.
The details that separate candidates: the tar/gzip division of labour (why the double extension exists at all); why compressing already-compressed data is wasted CPU; and choosing by context — gzip for logs in flight, zstd where speed matters, zip only when a Windows recipient is involved.
Part E — Toolkit
E1. Production practice — symptoms and fixes
| Symptom | What is really happening | What to run | The fix |
|---|---|---|---|
| Terminal prints gibberish after viewing a file | You cat'ed a binary; escape bytes reprogrammed the terminal | reset (type it blind, press Enter) | Terminal reborn; next time file first, less for the brave |
| No such file or directory — but ls shows the file right there | It is a symlink whose target is gone (dangling) | ls -l thefile — look for -> · file thefile | Recreate or repoint the target; ln -sfn newtarget thefile |
| tail -f shows nothing and "hangs" | It is following; the file simply has no new lines | Append a test line from another terminal | Nothing to fix — Ctrl+C when done watching |
| Deleted a huge log; disk usage did not drop | A running program still holds the deleted inode open — name gone, data alive | stat/link-count thinking for now | Full diagnosis and fix in Module 12 (lsof + truncate) — recognise the mechanism today |
| Extracted an archive; files scattered everywhere | Tarbomb: entries had no shared top directory | tar -tzf archive (belatedly) to see what landed | Extract into a fresh dir with -C next time; use the listing to clean up |
| "I edited the file but the program sees old values" | Often: edited a copy, a dangling link's link file, or the wrong of two hard-linked names — or the program reads config only at startup | ls -li both paths · stat the real one · diff expectation vs disk | Edit the inode the program actually reads; restarting services arrives in Module 11 |
E2. Capstone — four tickets
🎫 Ticket 1 — "Terminal is printing hieroglyphics after I looked at a log"
Ticket text: "I ran cat on a file in the app's data directory and now my terminal prints boxes and weird symbols even for ls. Is the server compromised?"
Worked answer: not compromised — the file was binary, and among its bytes were terminal control sequences that switched the terminal's character mode. The screen is confused, not the server. Fix: type reset and press Enter (yes, blind — it works even when unreadable) and the terminal reinitializes. Prevention ritual: file suspicious.thing before viewing; anything not reporting text gets less (which shows binaries safely) or is simply not for eyes. Bonus diagnosis for the ticket: file on that data directory's contents will likely report what the app really stores — databases and binary formats live in data dirs; human-readable logs live in /var/log.
🎫 Ticket 2 — "Config file exists but the app says No such file or directory"
Ticket text: "After last night's cleanup job, the app fails at startup: config.yml: No such file or directory. But ls in the config directory shows config.yml sitting right there. Ghost file?"
Worked answer: ls -l config.yml — the leading l and -> /shared/configs/app/config.yml reveal it was always a symlink, and the cleanup deleted its target. file config.yml confirms: broken symbolic link to …. The app's error names the link because that is the path it opened; the kernel failed while following it. Fix: restore the target (from backup, or the cleanup job's trash if any), or repoint: ln -sfn /new/location/config.yml config.yml. Prevention: cleanup jobs must never assume "no name points here" — nothing tracks symlinks pointing at a file, so deletion policies need an inventory of link consumers, or links should point into managed directories that cleanup respects.
🎫 Ticket 3 — "Vendor sent an archive; last time we extracted one it made a mess"
Ticket text: "We received data-export.tar.gz from a vendor. Last quarter someone extracted their archive in /srv/imports and files went everywhere, overwriting two existing reports. Give the team a safe standard procedure."
Worked answer, as the procedure: 1) file data-export.tar.gz — confirm it is actually gzip data, not a mislabelled zip. 2) tar -tzf data-export.tar.gz — read the listing: do all paths share one top directory? Any absolute paths or ../ entries (treat as hostile — refuse and escalate)? 3) mkdir /srv/imports/vendor-2026q3 — a fresh, empty room. 4) tar -xzf data-export.tar.gz -C /srv/imports/vendor-2026q3. 5) ls -R the result and compare against the step-2 listing. Last quarter's mess was a tarbomb plus tar's silent overwrite; the empty target directory makes both impossible, and the pre-listing catches malice for free.
🎫 Ticket 4 — "Two config files are haunted — editing one changes the other"
Ticket text: "In the app directory, app.conf and app-backup.conf always have identical content. We edit one, the other changes too. We've deleted and recreated app-backup.conf as a copy, but an engineer re-ran the old setup script and it's haunted again."
Worked answer: ls -li app.conf app-backup.conf — identical inode numbers and a link count of 2: they are hard links, two names for one file, so "both changing" is one file changing under two labels. The setup script evidently uses ln where the team expected cp. There is no original and no copy — and deleting one name never affected the shared inode, which is why recreating it as a true cp fixed things until the script re-linked it. Fix: correct the script (cp for an independent backup, or ln -s if a pointer was intended — visible as such in ls). Detection habit: any time two files move in lockstep, ls -li settles it in one line.
E3. Documentation reference
| Topic | Authoritative source | Verified link |
|---|---|---|
| Reading files | cat(1), less(1), head(1), tail(1) | cat(1) · less(1) · head(1) · tail(1) |
| Identifying and measuring | file(1), wc(1), diff(1) | file(1) · wc(1) · diff(1) |
| Editors | GNU nano manual; Vim help | nano manual · vimhelp.org |
| Inodes, links, timestamps | inode(7), symlink(7), ln(1), stat(1), touch(1) | inode(7) · symlink(7) · ln(1) · stat(1) · touch(1) |
| Archiving and compression | tar(1); GNU Gzip manual | tar(1) · GNU Gzip manual |
| The theory beneath this module | Companion track | Untitled |
E4. Self-assessment
Answer out loud, without notes. The section number tells you where to re-read.
- You are handed an unknown file on a production box. What is your first command, and what are your next moves for each of its three likely answers (text, binary, symlink)? (A1)
- Why does less open a 10 GB file instantly while cat on the same file is a mistake? (A1–A2)
- tail -f has printed nothing for five minutes. List the possible meanings and how you would distinguish them. (A3)
- Recite the vim survival loop, including the escape hatch for "I don't know what state this is in". (B2)
- What is the complete safe-change ritual for editing a config file, and what does each step prove? (B1–B3)
- In diff output, decode 2,3c2,4, <, and >. What does no output mean? (B3)
- What does an inode contain, and what — famously — does it not contain? Where does that thing live instead? (C1)
- After ln a b, which is the original? What happens to the data when you rm a? When does data actually get freed? (C2)
- A file visibly exists in ls, but reading it says No such file or directory. Reconstruct the mechanism and the two commands that confirm it. (C3)
- mtime, atime, ctime: which one does ls -l show, which one can nobody set by hand, and which one moved when you renamed the file? (C4)
- Why is the extension .tar.gz two tools, and what does each contribute? (D1–D2)
- You receive an unknown archive. Recite the five-step safe extraction procedure and what each step defends against. (D3, Ticket 3)
E5. Sources
GeeksforGeeks — Linux Interview Questions (70+) (updated July 2026) · InterviewBit — Linux Interview Questions (2025) · Adaface — 96 Linux Commands interview questions (September 2024).
Corpus honesty note: links and inodes are interview staples with deep published coverage. Editor questions are thin — only InterviewBit's vi-modes question appears in recent sets, and nano is asked about nowhere; editors are tested by watching you use one, not by quizzing. tar/gzip questions exist (Adaface) but stay shallow; the tarbomb/safety material here goes beyond the published corpus because it is what actually goes wrong at work.
All documentation links on this page were fetched and confirmed reachable on 2 September 2026.