A Linux server is administered by editing text files. A service's configuration is text, the records it produces are text, Tramontana's booking inventory is semicolon-separated text, and even the information the kernel exposes in /proc is text. The Unix philosophy you met in Module 1 — "plain text as the universal interface" — has this very concrete consequence: half of the work of administration consists of reading and modifying text files.
This lesson gives you the two halves of that skill. The first, viewing content without opening anything: which command to use for a three-line file, which one for a two-million-line file, and which one for a log that is growing right now while Marta asks you what is going on. The second, editing: nano in full, because it is what you will use every day, and the bare minimum of vim, because the day you log into a freshly installed server or a container it may be the only thing there.
And in between, a convention the course adopts from here on and never abandons: before editing a configuration file, you make a copy.
Contents
catandtac: the whole file at onceless: the real viewerheadandtail: the ends- Following a file live:
tail -fandtail -F nlandwc: numbering and counting- A survey of terminal editors
nanoin full- Survival
vim - The golden rule: back up before editing
- Files that are not text:
file,strings,xxdandreset - Comparing versions:
diffandsdiff
cat and tac: the whole file at once
cat and tac: the whole file at oncecat (concatenate) dumps one or more files to the screen:
operator@srv-tramontana:~$ cat data/houses.txt
mas-figueres;Mas Figueres;Girona;6
can-ventos;Can Ventós;Empordà;4
la-solana;La Solana;Pallars;8
cal-ferrer;Cal Ferrer;Berguedà;10Its name comes from its original purpose, which was not to display but to concatenate:
Useful options:
| Option | What it does |
|---|---|
-n |
Numbers every line |
-b |
Numbers only the non-empty ones |
-A |
Shows invisible characters ($ end of line, ^I tab) |
-s |
Squeezes several consecutive blank lines into one |
-A is an underrated diagnostic tool:
Those ^M are Windows carriage returns. The $ marks the real end of line. It is the same pathology file detected with "CRLF line terminators" in lesson 02-03, seen now character by character. A service that reads port=8080\r will try to open a port called 8080\r and will fail with a message that helps nobody.
The "useless use of cat"
There is an established expression in Unix culture, Useless Use of Cat (UUOC), for this:
# Useless use of cat
cat /var/log/tramontana/errors.log | grep "ERROR"
# Correct
grep "ERROR" /var/log/tramontana/errors.loggrep knows how to open files. Putting a cat in front creates an extra process and a pipe for nothing. With a small file it makes no difference; with one of several gigabytes, the cat forces the whole contents to be read and pushed through the pipe instead of letting grep read it directly.
This is not purism: the practical reason is that many tools lose functionality when they receive their input through a pipe instead of a file name. grep given a file can tell you the line number and the file name; through a pipe it only sees an anonymous stream. And wc -l file prints the name, whereas cat file | wc -l does not.
That said, there is a legitimate use: when you really are concatenating several files, or when you are building a chain exploratively and clarity matters more to you than performance.
The honest rule: cat is for files that fit on the screen. For a large file, cat throws thousands of lines at you and you only see the last twenty-five. That is what less is for.
tac is cat backwards: it shows the file starting from the last line.
operator@srv-tramontana:~$ tac data/houses.txt
cal-ferrer;Cal Ferrer;Berguedà;10
la-solana;La Solana;Pallars;8
can-ventos;Can Ventós;Empordà;4
mas-figueres;Mas Figueres;Girona;6It sounds like a curiosity and it is very useful with logs, because the most recent entries are at the end and tac puts them on top.
less: the real viewer
less: the real viewerless shows a file page by page, without loading it entirely into memory. That last point is the important one: it opens a 10 GB file instantly, because it only reads what it is displaying.
You already know the keystrokes from lesson 02-02, because man uses less underneath. Here they are in full, including the ones that only make sense on files:
| Key | Action |
|---|---|
Space / f |
Forward one screen |
b |
Back one screen |
d / u |
Half a screen forward / back |
Arrows, j / k |
Line by line |
g / G |
Start / end of the file |
50g |
Go to line 50 |
/text |
Search forwards |
?text |
Search backwards |
n / N |
Next / previous match |
&text |
Filter: show only the matching lines |
-N (inside) |
Toggle line numbers on or off |
-S (inside) |
Toggle wrapping of long lines on or off |
m + letter |
Mark this position |
' + letter |
Return to the mark |
F |
Switch to live follow mode |
v |
Open the file in the editor |
h |
Help |
q |
Quit |
Command-line options worth knowing:
# With line numbers
operator@srv-tramontana:~$ sudo less -N /var/log/tramontana/errors.log
# Without wrapping long lines: you scroll sideways with the arrows
operator@srv-tramontana:~$ sudo less -S /var/log/tramontana/access.log
# Open straight at the end (the newest part of a log)
operator@srv-tramontana:~$ sudo less +G /var/log/tramontana/errors.log
# Open in live follow mode
operator@srv-tramontana:~$ sudo less +F /var/log/tramontana/errors.log-S deserves an explanation. By default, less breaks long lines so that they fit, and a single line can take up five rows of the screen. In a log with long lines that makes reading impossible. With -S each line occupies one row and the rest stays off-screen; you see it by scrolling with the left and right arrows. For an access.log with two-hundred-character lines, -S is the difference between being able to read it and not.
& is the hidden gem. Inside less, type &ERROR and Enter: the screen shows only the lines containing "ERROR", hiding the rest. It is an instant filter without leaving the viewer. You clear it with & and a blank Enter.
+F turns less into a tail -f with superpowers: it follows the file live, and with Ctrl+C you leave follow mode and can search and navigate through what has already been written, without closing anything. With F you go back to following. It is the best tool there is for watching a log during an incident.
more and why less replaced it
more is the original Unix pager. It is still installed for compatibility:
more |
less |
|
|---|---|---|
| Forward | Yes | Yes |
| Backward | No (in the original version) | Yes |
| Search backwards | No | Yes |
| Opening huge files | Reads them whole | Only what it displays |
| Quitting before the end | Yes, with q |
Yes |
| On finishing | Exits and leaves the text on screen | Clears and returns to the prompt |
more's limitation was brutal: if you went one line too far, there was no going back. You had to reopen the file from the beginning. less was born precisely to fix that, and hence its name, a play on the English saying "less is more".
Today there is no reason to use more, except one: on a minimal system or in rescue mode less may not be installed while more is. Just know that it exists.
One behaviour of less worth knowing about and sometimes disabling: on exit it clears the screen, leaving no trace of what you saw. If you prefer the content to stay, there is the -X option. And if you want it not to paginate when the content fits on one screen, -F (the capital option, distinct from the F key used inside). The combination less -FX is the one many administrators leave configured by default.
head and tail: the ends
head and tail: the endsYou hardly ever need the whole file: you need the beginning or the end.
operator@srv-tramontana:~$ head data/bookings.csv
id;date;house;guest;nights;amount
1001;2026-07-03;mas-figueres;Nuria Prat;4;620.00
1002;2026-07-05;can-ventos;Oriol Sala;2;280.00
1003;2026-07-08;la-solana;Marc Aliaga;7;1190.00
1004;2026-07-11;cal-ferrer;Berta Mir;3;540.00
1005;2026-07-14;mas-figueres;Jordi Camps;5;775.00
1006;2026-07-18;can-ventos;Aina Roca;2;280.00
1007;2026-07-21;la-solana;Pau Serra;6;1020.00
1008;2026-07-25;cal-ferrer;Elena Vila;4;720.00
1009;2026-07-29;mas-figueres;Sergi Bosch;3;465.00Ten lines by default in both commands.
| Option | head |
tail |
|---|---|---|
-n N |
First N lines | Last N lines |
-n +N |
— | From line N to the end |
-n -N |
Everything except the last N | — |
-c N |
First N bytes | Last N bytes |
-q |
No headers when given several files | Same |
-f |
— | Follow the file live |
operator@srv-tramontana:~$ head -n 1 data/bookings.csv
id;date;house;guest;nights;amount
operator@srv-tramontana:~$ tail -n 3 data/bookings.csv
1023;2026-08-14;can-ventos;Ivan Puig;3;420.00
1024;2026-08-16;la-solana;Clara Font;5;850.00
1025;2026-08-17;cal-ferrer;Roger Mas;2;360.00tail's -n +N form is the one used to skip the header of a CSV:
operator@srv-tramontana:~$ tail -n +2 data/bookings.csv | head -n 3
1001;2026-07-03;mas-figueres;Nuria Prat;4;620.00
1002;2026-07-05;can-ventos;Oriol Sala;2;280.00
1003;2026-07-08;la-solana;Marc Aliaga;7;1190.00"From line 2 to the end" discards the header. It is a pattern you will use constantly when processing data in lesson 03-05.
-c works with bytes and is useful for peeking at an unknown file without dumping the whole thing:
operator@srv-tramontana:~$ head -c 100 /opt/tramontana/app/executable | xxd | head -n 3
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0300 3e00 0100 0000 6013 0000 0000 0000 ..>.....`.......
00000020: 4000 0000 0000 0000 e8ad 4c00 0000 0000 @.........@L....With several files, both add a header giving the name:
operator@srv-tramontana:~$ head -n 2 data/houses.txt data/bookings.csv
==> data/houses.txt <==
mas-figueres;Mas Figueres;Girona;6
can-ventos;Can Ventós;Empordà;4
==> data/bookings.csv <==
id;date;house;guest;nights;amount
1001;2026-07-03;mas-figueres;Nuria Prat;4;620.00
- Following a file live:
tail -f and tail -F
tail -f and tail -FThis is the section you will use most in your professional life. When something fails in production, the first thing you do is open the log and watch it while the problem is reproduced.
operator@srv-tramontana:~$ sudo tail -f /var/log/tramontana/errors.log
2026-08-18 11:52:03 WARN slow database connection (1240 ms)
2026-08-18 11:52:44 ERROR failed to send confirmation email booking=1024
2026-08-18 11:53:01 INFO retry scheduled in 60 sThe command does not finish: it stays waiting and displays each new line as the application writes it. You exit with Ctrl+C.
The critical difference between -f and -F
You already glimpsed it when settling Luis's question in lesson 02-02. Now in detail, because it is one of those things you learn through an incident if you do not learn it beforehand.
flowchart TD
A["tail -f errors.log"] --> B["Follows the DESCRIPTOR<br/>of the open file"]
C["tail -F errors.log"] --> D["Follows the NAME<br/>and reopens if it changes"]
B --> E["logrotate at midnight:<br/>errors.log -> errors.log.1<br/>a new errors.log is created"]
D --> E
E --> F["-f keeps watching the<br/>renamed file: SHOWS NOTHING<br/>and gives no warning"]
E --> G["-F detects the change,<br/>reopens and keeps displaying"]
logrotate is the mechanism that stops logs growing without end: every night it renames errors.log to errors.log.1, compresses it and creates an empty errors.log. Lesson 05-06 covers it.
If you left a tail -f running, after the rotation it carries on reading a file nobody writes to any more. The screen sits still and there is no message to warn you. You can spend twenty minutes convinced that the application is not logging errors when in fact it is writing them somewhere else.
operator@srv-tramontana:~$ sudo tail -F /var/log/tramontana/errors.log
tail: '/var/log/tramontana/errors.log' has been replaced; following end of new file
2026-08-19 00:00:12 INFO daily cycle start-F warns you and reattaches. For production logs, always -F.
Options that go well with it:
# Start by showing the last 50 lines and then follow
operator@srv-tramontana:~$ sudo tail -F -n 50 /var/log/tramontana/errors.log
# Follow two files at once: tail marks which one each block comes from
operator@srv-tramontana:~$ sudo tail -F /var/log/tramontana/access.log /var/log/tramontana/errors.log
==> /var/log/tramontana/errors.log <==
2026-08-18 11:52:44 ERROR failed to send confirmation email booking=1024
==> /var/log/tramontana/access.log <==
2026-08-18 11:52:45 POST /bookings 500 house=la-solana
# Finish automatically if process 4127 dies
operator@srv-tramontana:~$ sudo tail -F --pid=4127 /var/log/tramontana/errors.logThat second example, following the access log and the error log at the same time, is exactly how diagnosis works: you see the error and, right next to it, the HTTP request that caused it.
Where less +F beats tail -F: if while watching the log you need to search backwards for something that already happened, tail will not let you and less +F will (Ctrl+C, search with /, and F to go back to following). The rule: tail -F to monitor, less +F to investigate.
nl and wc: numbering and counting
nl and wc: numbering and countingnl numbers lines in a configurable way, with more control than cat -n:
operator@srv-tramontana:~$ nl data/houses.txt
1 mas-figueres;Mas Figueres;Girona;6
2 can-ventos;Can Ventós;Empordà;4
3 la-solana;La Solana;Pallars;8
4 cal-ferrer;Cal Ferrer;Berguedà;10
operator@srv-tramontana:~$ nl -ba -w 3 -s ': ' data/houses.txt
1: mas-figueres;Mas Figueres;Girona;6
2: can-ventos;Can Ventós;Empordà;4-ba numbers every line (by default it skips the empty ones), -w sets the width and -s the separator. It is useful when you want to send somebody a fragment of configuration with line numbers so that you can refer to them over the phone.
wc (word count) counts:
Three numbers: lines, words, bytes. The options pick which one:
| Option | Counts |
|---|---|
-l |
Lines |
-w |
Words |
-c |
Bytes |
-m |
Characters (different from bytes with UTF-8) |
-L |
Length of the longest line |
operator@srv-tramontana:~$ wc -l data/bookings.csv
26 data/bookings.csv
operator@srv-tramontana:~$ sudo wc -l /var/log/tramontana/*.log
412 /var/log/tramontana/access.log
87 /var/log/tramontana/errors.log
499 totalA nuance of -c against -m that matters with Catalan and Spanish names:
operator@srv-tramontana:~$ echo -n "Can Ventós" | wc -c
11
operator@srv-tramontana:~$ echo -n "Can Ventós" | wc -m
10Ten characters, eleven bytes: the ó takes two bytes in UTF-8. When you count the length of data fields, -m is what you want.
And a practical detail: wc -l on a CSV counts the header too. The real bookings are 25, not 26.
- A survey of terminal editors
| Editor | Learning curve | Present by default | Power | When to use it |
|---|---|---|---|---|
| nano | Very gentle | Ubuntu and Debian, yes | Medium | Your everyday editor. Quick configuration edits |
| vim | Steep | Almost universal (vi in POSIX) |
Very high | Other people's servers, containers, rescue mode |
| emacs | Very steep | Rarely | Maximum | If you already use it; not something you learn in order to administer |
| VS Code over SSH | Gentle | No | High | Development, projects with many files |
ed |
Extreme | Always | Low | Only if the terminal is so broken that nothing else works |
The sensible decision for a systems administrator is to learn nano well and just enough vim:
nanofor your day to day. It is on Ubuntu, on Debian and on most server distributions. It shows the shortcuts on screen. Nobody has ever lost time learning it.vimbecause one day there will be nonano. Minimal Docker containers, RHEL images without optional packages, systems in rescue mode, client servers with restrictive policies. All of them haveviorvim, because POSIX requires it. Logging into a downed server and not knowing how to get out ofvimis a real and avoidable situation.
About VS Code or graphical editors over SSH: the Remote-SSH extension lets you edit remote files through a comfortable graphical interface. It is excellent for development. For administration it has three serious drawbacks: it installs an agent on the server (which consumes memory and may not be permitted), it does not work in rescue mode or before the system has fully booted, and it is not available when you connect from the physical console or from your phone while on call. Use it to work on the application's code; do not depend on it to administer.
emacs is an extraordinary environment, but learning it from scratch to edit four configuration files does not pay off. If you already use it, carry on.
nano in full
nano in fullThe screen has three zones: the title at the top, the text in the middle and two rows of shortcuts at the bottom. Those two rows are the reason nano is not forgotten.
Reading the shortcut bar
The notation is fixed and has to become second nature:
^means Ctrl.^OisCtrl+O.M-means Meta, which on a modern keyboard is Alt (orEscpressed and released beforehand).M-UisAlt+U.
With that, the bottom bar is the complete documentation, permanently visible.
Shortcuts by category
Movement (besides the arrow keys, which always work):
| Shortcut | Action |
|---|---|
Ctrl+A / Ctrl+E |
Start / end of the line |
Ctrl+Y / Ctrl+V |
Page up / down |
Alt+\ / Alt+/ |
Start / end of the file |
Ctrl+_ |
Go to a specific line number |
Alt+G |
The same, in recent versions |
Notice that Ctrl+A and Ctrl+E are the same as on the Bash command line. That is no coincidence: both follow the readline convention.
Editing:
| Shortcut | Action |
|---|---|
Ctrl+K |
Cut the current line (or the selection) |
Ctrl+U |
Paste what was cut |
Alt+6 |
Copy without cutting |
Ctrl+6 or Alt+A |
Mark the start of a selection |
Alt+U |
Undo |
Alt+E |
Redo |
Ctrl+D |
Delete the character under the cursor |
To cut several lines, repeat Ctrl+K: they accumulate in the clipboard and Ctrl+U pastes them all together.
Search and replace:
| Shortcut | Action |
|---|---|
Ctrl+W |
Search |
Alt+W |
Repeat the last search |
Ctrl+\ |
Search and replace |
Ctrl+C |
Show the current position (line and column) |
The replacement with Ctrl+\ asks for the text to find, then the replacement, and for each match it asks: Y to replace this one, N to skip it, A for all of them at once.
File:
| Shortcut | Action |
|---|---|
Ctrl+O |
Save (asks you to confirm the name; Enter for the same one) |
Ctrl+R |
Insert another file at the cursor position |
Ctrl+X |
Exit (asks if there are unsaved changes) |
Ctrl+T |
File browser when saving |
The complete basic flow: you open, edit, Ctrl+O, Enter, Ctrl+X. Five keystrokes.
If you try to exit with unsaved changes, nano asks:
A minimal ~/.nanorc
Four settings that improve the experience enormously:
# Show line numbers set linenumbers # Syntax highlighting include "/usr/share/nano/*.nanorc" # Do not wrap long lines automatically: CRITICAL in configuration files set nowrap # Turn tabs into spaces and use 4 set tabstospaces set tabsize 4 # Show the cursor position permanently set constantshow # Make a backup copy when saving set backup set backupdir "/home/operator/.nano-backups"
The two that really matter:
set nowrap stops nano automatically breaking long lines when it saves. Without it, editing a configuration file with a long directive can break it silently: the line is split in two, the service reads half a directive and fails. It is the cause of more than one wasted afternoon.
set backup saves a copy of the original file every time you save. It does not replace the convention in section 9, but it is a free extra safety net.
- Survival
vim
vimThe goal here is not for you to master vim. It is for you to be able to edit a file and get out without wrecking anything on a machine where there is nothing else. That is about fifteen commands.
The concept that explains everything: modes
vim is not an editor where you type; it is an editor where you give orders, and one of those modes consists of typing text.
flowchart LR
N["NORMAL MODE<br/>(on opening)<br/>Keys are commands"]
I["INSERT MODE<br/>Keys type text"]
C["COMMAND MODE<br/>Orders starting with :"]
N -->|"i, a, o, I, A, O"| I
I -->|"Esc"| N
N -->|":"| C
C -->|"Enter or Esc"| N
Two rules follow from this, and they solve 90% of the trouble:
- When you open
vimyou are in NORMAL mode. If you type, no text appears: commands are executed. Escalways brings you back to normal mode. If you are lost, pressEsctwice and you are on familiar ground.
Getting in and out
| Command | What it does |
|---|---|
:w |
Save |
:q |
Quit (fails if there are unsaved changes) |
:wq or :x |
Save and quit |
ZZ |
Save and quit (in normal mode, without :) |
:q! |
Quit discarding the changes |
:w name |
Save under another name |
:w !sudo tee % |
Save as root a file you opened without permissions |
:q! is the most important command in this section. If you have ended up in vim by accident, if you have typed something you did not mean to, if you do not know what you have done: Esc, then :q!, then Enter. You get out without saving and the file is left as it was.
That :w !sudo tee % solves a very common situation: you opened /etc/tramontana/app.conf without sudo, made all your changes, and on saving it tells you the file is read-only. Instead of losing the work, that command writes it through sudo.
Typing text
| Key | Enters insert mode... |
|---|---|
i |
before the cursor |
a |
after the cursor |
I |
at the start of the line |
A |
at the end of the line |
o |
on a new line below |
O |
on a new line above |
A and o are the ones most used when editing configuration: appending to the end of a line or opening a new line.
Moving around
| Key | Movement |
|---|---|
h j k l |
Left, down, up, right (the arrow keys work too) |
w / b |
Next / previous word |
0 / $ |
Start / end of line |
gg |
Start of the file |
G |
End of the file |
42G or :42 |
Go to line 42 |
Ctrl+F / Ctrl+B |
Page forward / back |
Editing
| Command | What it does |
|---|---|
x |
Delete the character under the cursor |
dd |
Cut the whole line |
3dd |
Cut three lines |
dw |
Delete to the end of the word |
yy |
Copy the line (yank) |
p |
Paste below |
P |
Paste above |
u |
Undo |
Ctrl+R |
Redo |
. |
Repeat the last command |
The logic of vim is that commands compose: d (delete) + w (word) = dw. 3 + dd = three lines. Understanding that composition is what makes people addicted to vim, and also what makes its curve steep. For survival, memorise dd, yy, p and u.
u is your parachute. Pressing it repeatedly undoes everything back to the initial state.
Searching and substituting
| Command | What it does |
|---|---|
/text |
Search forwards |
?text |
Search backwards |
n / N |
Next / previous |
:%s/old/new/g |
Substitute throughout the file |
:%s/old/new/gc |
The same, asking in each case |
:s/old/new/g |
Only on the current line |
A breakdown of :%s/old/new/g, because every part counts:
%— on every line of the files— substitute/old/new/— what for whatg— global: every occurrence on each line, not just the first
The variant with c at the end asks before each substitution. Always use it on a production file.
vimtutor
vim comes with an interactive tutorial of about thirty minutes:
It is, without exaggeration, the best half-hour investment you can make in this module. It will not make you an expert, but after doing it once you will never again be trapped on a server.
A note on vi versus vim: vi is the original Unix editor and vim (Vi IMproved) is its modern version. On Ubuntu, vi is usually a link to vim. On minimal systems you can come across the real vi, with fewer features (no colours, no multiple u), but everything in this section works the same.
- The golden rule: back up before editing
This is not a trick: it is a working convention that the course adopts from here on and never abandons.
Before modifying any configuration file, you make a dated copy.
operator@srv-tramontana:~$ sudo cp /etc/tramontana/app.conf /etc/tramontana/app.conf.bak-$(date +%F)
operator@srv-tramontana:~$ ls -l /etc/tramontana/
total 8
-rw-r----- 1 root tramontana 512 Aug 18 08:47 app.conf
-rw-r----- 1 root tramontana 512 Aug 18 12:15 app.conf.bak-2026-08-18$(date +%F) is command substitution: the shell runs date +%F, which returns 2026-08-18, and puts the result on the line. The full mechanism is explained in lesson 03-01; adopt it now as a formula.
Why exactly like this:
| Element | Reason |
|---|---|
| In the same directory | You find it instantly, without having to remember where you left it |
The .bak-DATE suffix |
You know when it is from without opening anything |
cp and not mv |
The original stays where it is and the service never notices |
| The date in ISO format | It sorts chronologically when you list with ls |
| Before, not after | Afterwards you no longer have the original |
If you are going to make several changes on the same day, add the time:
operator@srv-tramontana:~$ sudo cp /etc/tramontana/app.conf /etc/tramontana/app.conf.bak-$(date +%F-%H%M)The complete procedure for a configuration edit, which is what gets done in production:
# 1. Backup
operator@srv-tramontana:~$ sudo cp /etc/tramontana/app.conf /etc/tramontana/app.conf.bak-$(date +%F)
# 2. Edit
operator@srv-tramontana:~$ sudo nano /etc/tramontana/app.conf
# 3. See EXACTLY what you changed
operator@srv-tramontana:~$ sudo diff -u /etc/tramontana/app.conf.bak-2026-08-18 /etc/tramontana/app.conf
# 4. (If the service has a validator, use it before reloading)
# 5. Reload the service and check that it starts
# 6. If something goes wrong: back out in one second
operator@srv-tramontana:~$ sudo cp /etc/tramontana/app.conf.bak-2026-08-18 /etc/tramontana/app.confStep 3 is what turns the copy into more than an insurance policy: it lets you verify your own change before applying it. That is where diff comes in, in section 11.
A warning about where to leave the copies: in some directories, leaving loose files has side effects. A .bak in /etc/sudoers.d/ or in /etc/apt/sources.list.d/ can be read by the system as if it were valid configuration. In those cases the copy goes outside the directory, for example in /home/operator/config-backups/. We will see this when we touch those directories in Module 5.
- Files that are not text:
file, strings, xxd and reset
file, strings, xxd and resetWhy you must not cat a binary
What happens: a stream of rubbish appears on screen, it beeps several times, and the terminal stops working properly. The letters you type come out as strange symbols, Enter does not move to a new line, the prompt is illegible.
The explanation is concrete and makes sense. A terminal interprets certain byte sequences as control commands, not as text: change colour, move the cursor, and — this is the one that causes the problem — switch the character set to graphics mode. A binary file contains arbitrary bytes, and among millions of random bytes the sequence that activates that mode will sooner or later appear. From then on, the terminal draws line-drawing symbols instead of letters.
Nothing has broken. You just have to restore the state:
Type it even if you cannot see what you are typing, and press Enter. If reset is not available:
And if nothing works, closing the SSH session and logging in again always fixes it, because the terminal is reinitialised.
Prevention: run file before opening an unknown file. It takes a second and avoids the whole problem.
operator@srv-tramontana:~$ file /opt/tramontana/app/executable
/opt/tramontana/app/executable: ELF 64-bit LSB pie executable, x86-64less also protects you: on detecting that a file is binary it warns with "file" may be a binary file. See it anyway?.
strings: extracting the text from a binary
operator@srv-tramontana:~$ strings /opt/tramontana/app/executable | head -n 20
/lib64/ld-linux-x86-64.so.2
libc.so.6
Tramontana Bookings
3.2.1
ERROR: cannot read configuration at %s
db_host
db_port
db_name
Booking created successfully
...strings looks for sequences of printable characters at least four bytes long. Real uses:
- Finding out the version of an undocumented binary.
- Finding the paths a program has compiled inside it (here you can see that it reads a configuration file).
- Seeing the error messages it can emit, which helps you interpret one you do not understand.
- In security analysis, spotting suspicious strings inside an unknown binary.
| Option | What it does |
|---|---|
-n N |
Minimum length (4 by default) |
-t x |
Shows the offset in hexadecimal |
-a |
Scans the whole file, not just the data sections |
xxd: seeing the bytes
operator@srv-tramontana:~$ xxd /opt/tramontana/app/executable | head -n 4
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0300 3e00 0100 0000 6013 0000 0000 0000 ..>.....`.......
00000020: 4000 0000 0000 0000 e8ad 4c00 0000 0000 @.........@L....
00000030: 0000 0000 4000 3800 0d00 4000 2100 2000 [email protected]...@.!. .Three columns: the offset in hexadecimal, the bytes in hexadecimal, and their representation as text (non-printable ones come out as dots).
The first four bytes, 7f 45 4c 46, are \x7fELF: the magic number that identifies a Linux executable. It is exactly what file reads to make its diagnosis.
Where xxd is genuinely useful: for seeing invisible characters in a text file that is causing trouble.
operator@srv-tramontana:~$ xxd /tmp/config-luis.conf | head -n 2
00000000: efbb bf70 6f72 743d 3830 3830 0d0a 7469 ...port=8080..ti
00000010: 6d65 6f75 743d 3330 0d0a meout=30..Two pathologies in plain sight:
ef bb bfat the start is the UTF-8 BOM, an invisible marker that some Windows editors add. The program reading the file sees the first key as\xef\xbb\xbfportinstead ofport, does not recognise it, and uses the default value. It is a fault that can cost hours if you do not know it exists.0d 0aat the end of each line is CRLF, the Windows line endings.
Neither of those two things is visible with cat, and both are obvious with xxd. That is why it is a diagnostic tool, not a curiosity.
Related utilities worth knowing: hexdump -C produces almost identical output and is available on more systems; od -c is the POSIX classic.
- Comparing versions:
diff and sdiff
diff and sdiffThe most frequent question after something stops working is "what changed?". diff answers it.
operator@srv-tramontana:~$ sudo diff /etc/tramontana/app.conf.bak-2026-08-18 /etc/tramontana/app.conf
7c7
< max_connections=50
---
> max_connections=200That classic format is compact but cryptic. The unified format, -u, is the one used today and the one Git and every modern tool understands:
operator@srv-tramontana:~$ sudo diff -u /etc/tramontana/app.conf.bak-2026-08-18 /etc/tramontana/app.conf
--- /etc/tramontana/app.conf.bak-2026-08-18 2026-08-18 12:15:03.000000000 +0200
+++ /etc/tramontana/app.conf 2026-08-18 12:31:47.000000000 +0200
@@ -4,7 +4,8 @@
db_port=5432
db_name=bookings
-max_connections=50
+max_connections=200
+query_timeout=30
log_level=infoHow to read it:
| Element | Meaning |
|---|---|
--- |
The original file, with its date |
+++ |
The new file, with its date |
@@ -4,7 +4,8 @@ |
Hunk: from line 4, 7 lines in the original; from line 4, 8 in the new one |
| Line with a space | Context: unchanged |
Line with - |
Removed from the original |
Line with + |
Added in the new one |
In this example: max_connections was raised from 50 to 200 and query_timeout was added. Two changes, identified in three seconds.
Useful options:
| Option | What it does |
|---|---|
-u |
Unified format. The one you should use |
-r |
Recursive, for comparing directories |
-q |
Only says whether they differ, not how |
-i |
Ignores case |
-w |
Ignores all whitespace |
-B |
Ignores blank lines |
--color=always |
Colours the differences |
-y |
Output in two columns |
-r with -q is what you used to verify Luis's package in the previous lesson:
operator@srv-tramontana:~$ diff -rq /opt/tramontana/app /tmp/verification/app
operator@srv-tramontana:~$ echo $?
0No output and code 0: identical. With differences it would be:
operator@srv-tramontana:~$ diff -rq /opt/tramontana/app /tmp/verification/app
Files /opt/tramontana/app/version.txt and /tmp/verification/app/version.txt differ
Only in /opt/tramontana/app: new.htmldiff's exit codes are among those you have to look up in the manual, as you learned in lesson 02-02:
| Code | Meaning |
|---|---|
| 0 | The files are identical |
| 1 | They are different (this is not an error) |
| 2 | A real error occurred |
sdiff: side-by-side comparison
operator@srv-tramontana:~$ sudo sdiff -w 100 /etc/tramontana/app.conf.bak-2026-08-18 /etc/tramontana/app.conf
# Tramontana Bookings configuration # Tramontana Bookings configuration
db_host=127.0.0.1 db_host=127.0.0.1
db_port=5432 db_port=5432
db_name=bookings db_name=bookings
max_connections=50 | max_connections=200
> query_timeout=30
log_level=info log_level=infoThe markers in the middle:
|— the line is different on the two sides<— it is only on the left>— it is only on the right- (empty) — identical
sdiff is more readable for a visual review and worse for pasting into a report or applying as a patch. -w sets the total width; adjust it to your terminal.
sdiff -o output additionally enters an interactive merge mode, line by line, useful for reconciling two versions of a configuration by hand.
Related tools worth knowing: vimdiff (or vim -d) shows the differences in colour inside vim and lets you move changes from one side to the other; comm compares two sorted files line by line; and cmp compares byte by byte and is the one for binaries, where diff contributes nothing readable.
Common Mistakes and Tips
Running cat on a huge file. Thousands of lines fly past and you only see the last ones. less or tail.
Running cat on a binary. Broken terminal. file first, and reset if it is already too late.
Using tail -f on a log that rotates. It goes silent without warning. -F always in production.
Not knowing how to get out of vim. Esc, :q!, Enter. And do vimtutor once.
Editing configuration without a prior copy. The most expensive mistake in this lesson, and the easiest to avoid.
Letting nano wrap long lines. set nowrap in ~/.nanorc, or a directive split in two will break the service.
Editing a file with nano and forgetting the sudo. You do all the work and on saving you discover it is read-only. In vim there is a rescue (:w !sudo tee %); in nano you can save into /tmp with Ctrl+O and move it afterwards.
Tip: less +F instead of tail -F when you are investigating. You can pause and search backwards without closing anything.
Tip: less -S for logs with long lines. It completely changes the readability.
Tip: diff -u right after editing, always. Before reloading the service, look at what you have done. Many times you will spot an accidental change.
Tip: learn to use & inside less. Filtering without leaving the viewer is faster than any pipeline.
Exercises
Exercise 1: analysing a log
On /var/log/tramontana/access.log on srv-tramontana, and without using grep (which belongs to Module 3):
- How many lines does it have?
- Show the first and the last one.
- Show lines 100 to 105.
- Open the file with
less, go to the end, search backwards for the first occurrence of500and note the line. Exit without closing the session. - Leave the file being followed live in a way that survives a rotation.
Exercise 2: editing the configuration safely
Marta has approved raising the application's connection limit. You have to change max_connections from 50 to 200 in /etc/tramontana/app.conf and add a line query_timeout=30.
Carry out the complete safe-editing procedure, including verification of the change, and prepare a two-line summary for Marta with exactly what you have modified. Do it first with nano and repeat it with vim for practice.
Exercise 3: the file that does not work
Luis sends you a /tmp/config-luis.conf that "has the same configuration but the application does not read it properly". The application returns default values instead of the ones in the file.
At first sight it looks correct. Diagnose the problem with the tools from this lesson and explain to Luis why it happens and how to avoid it in future.
Solutions
Solution 1
# 1. Number of lines
operator@srv-tramontana:~$ sudo wc -l /var/log/tramontana/access.log
412 /var/log/tramontana/access.log
# 2. First and last
operator@srv-tramontana:~$ sudo head -n 1 /var/log/tramontana/access.log
2026-08-18 00:00:14 GET /houses 200 user=anon
operator@srv-tramontana:~$ sudo tail -n 1 /var/log/tramontana/access.log
2026-08-18 09:14:11 GET /houses 200 user=anon
# 3. Lines 100 to 105
operator@srv-tramontana:~$ sudo head -n 105 /var/log/tramontana/access.log | tail -n 6
2026-08-18 03:41:02 GET /bookings 200 user=mvidal
2026-08-18 03:41:19 POST /bookings 201 house=can-ventos
...The combination head -n 105 | tail -n 6 is the classic idiom for extracting a range: take the first 105 and out of those keep the last 6. Watch the arithmetic: lines 100 to 105 are six lines, not five.
A cleaner alternative with sed, which you will meet in lesson 03-05:
4. Inside less:
Gto go to the end.?500and Enter to search backwards (?is the reverse search; with/you would search forwards and from the end you would find nothing).Nto keep going backwards if you want to see more matches,nto come forwards again.- You can see the line number thanks to
-N. qto quit.
5. Rotation-proof following:
-F, not -f. With -f, when logrotate renames the file tonight, the command will carry on watching the old file and will show nothing more, without any warning. With -F it detects the replacement, announces it and reattaches to the new file.
If you also want to be able to investigate while you monitor:
Ctrl+C to pause and search, F to go back to following.
Solution 2
The nano version:
# 1. Backup BEFORE anything else
operator@srv-tramontana:~$ sudo cp /etc/tramontana/app.conf /etc/tramontana/app.conf.bak-$(date +%F)
operator@srv-tramontana:~$ ls -l /etc/tramontana/
-rw-r----- 1 root tramontana 512 Aug 18 08:47 app.conf
-rw-r----- 1 root tramontana 512 Aug 18 12:15 app.conf.bak-2026-08-18
# 2. Edit
operator@srv-tramontana:~$ sudo nano /etc/tramontana/app.confInside nano: Ctrl+W, type max_connections, Enter. The cursor goes to the line. Ctrl+K cuts it and you type the new one, or better: position yourself with End, delete the 50 and type 200. Then Ctrl+E (end of line), Enter for a new line, and type query_timeout=30. Save with Ctrl+O, Enter, and exit with Ctrl+X.
# 3. Verify exactly what has changed
operator@srv-tramontana:~$ sudo diff -u /etc/tramontana/app.conf.bak-2026-08-18 /etc/tramontana/app.conf
--- /etc/tramontana/app.conf.bak-2026-08-18 2026-08-18 12:15:03.000000000 +0200
+++ /etc/tramontana/app.conf 2026-08-18 12:31:47.000000000 +0200
@@ -4,7 +4,8 @@
db_port=5432
db_name=bookings
-max_connections=50
+max_connections=200
+query_timeout=30
log_level=info
# 4. Check that the file's format has not been broken
operator@srv-tramontana:~$ sudo cat -A /etc/tramontana/app.conf | tail -n 5
max_connections=200$
query_timeout=30$
$
log_level=info$That cat -A confirms there are no ^M and no split lines: every line ends cleanly with $.
The vim version, starting from the restored copy:
operator@srv-tramontana:~$ sudo cp /etc/tramontana/app.conf.bak-2026-08-18 /etc/tramontana/app.conf
operator@srv-tramontana:~$ sudo vim /etc/tramontana/app.confThe key sequence:
/max_connectionsand Enter — searches and places the cursor.A— insert at the end of the line.- Delete
50with backspace and type200. Esc— back to normal mode.o— opens a new line below and enters insert mode.- Type
query_timeout=30. Esc, then:wqand Enter.
An even quicker alternative for the first change, using substitution with confirmation:
The final c makes it ask before substituting. On a production file, always with c.
# 3 and 4 the same as before
operator@srv-tramontana:~$ sudo diff -u /etc/tramontana/app.conf.bak-2026-08-18 /etc/tramontana/app.confSummary for Marta:
Change applied to the Tramontana Bookings configuration (
/etc/tramontana/app.conf) on 18/08/2026 at 12:31: the simultaneous connection limit goes up from 50 to 200 and a maximum wait per query of 30 seconds has been added, so that a blocked query does not hold a connection indefinitely.A copy of the previous configuration has been kept (
app.conf.bak-2026-08-18), so reverting the change is a matter of seconds if we notice any unwanted effect. The change requires the service to be reloaded to take effect.
That report has what Marta needs: what changed, when, why and how to undo it.
Solution 3
cat does not show the problem because the problem is invisible by definition. Diagnosis step by step:
# Step 1: what kind of file is it really?
operator@srv-tramontana:~$ file /tmp/config-luis.conf
/tmp/config-luis.conf: Unicode text, UTF-8 (with BOM) text, with CRLF line terminatorsfile already gives the complete diagnosis in one line: UTF-8 BOM and CRLF terminators. Two problems, both caused by having edited the file in a Windows editor.
# Step 2: confirm it by looking at the control characters
operator@srv-tramontana:~$ cat -A /tmp/config-luis.conf
M-oM-;M-?port=8080^M$
timeout=30^M$
log_level=debug^M$
# Step 3: see it byte by byte to leave no doubt
operator@srv-tramontana:~$ xxd /tmp/config-luis.conf | head -n 2
00000000: efbb bf70 6f72 743d 3830 3830 0d0a 7469 ...port=8080..ti
00000010: 6d65 6f75 743d 3330 0d0a 6c6f 675f 6c65 meout=30..log_leWhat is happening exactly:
Problem 1: the BOM (ef bb bf). These are three invisible bytes at the start of the file, a marker some Windows editors insert to signal that the file is UTF-8. When the application reads the first line, it does not see the key port but \xef\xbb\xbfport. It does not recognise it as a valid key, so it uses the default value and carries on as if nothing had happened. It only affects the first line, which makes the fault even more confusing: it looks as though some options work and others do not.
Problem 2: CRLF (0d 0a). Each line ends with a carriage return plus a line feed, whereas Unix uses only the line feed. The application reads the value of timeout as 30\r, not as 30. If it expects a number, the conversion fails and it falls back to the default. And if the value were a path, it would try to open a file whose name ends in an invisible character, with an error message along the lines of No such file: /var/log/app.log in which the path looks perfectly correct.
The solution:
# A copy before touching anything, as always
operator@srv-tramontana:~$ cp /tmp/config-luis.conf /tmp/config-luis.conf.bak-$(date +%F)
# Install the specific tool
operator@srv-tramontana:~$ sudo apt install -y dos2unix
# Convert: dos2unix removes the BOM and the CRs in one go
operator@srv-tramontana:~$ dos2unix /tmp/config-luis.conf
dos2unix: converting file /tmp/config-luis.conf to Unix format...
# Verify
operator@srv-tramontana:~$ file /tmp/config-luis.conf
/tmp/config-luis.conf: ASCII text
operator@srv-tramontana:~$ cat -A /tmp/config-luis.conf
port=8080$
timeout=30$
log_level=debug$
operator@srv-tramontana:~$ xxd /tmp/config-luis.conf | head -n 1
00000000: 706f 7274 3d38 3038 300a 7469 6d65 6f75 port=8080.timeouNow the file starts directly with port and every line ends in 0a. The final comparison:
operator@srv-tramontana:~$ diff /tmp/config-luis.conf.bak-2026-08-18 /tmp/config-luis.conf
1,3c1,3
< port=8080
---
> port=8080
...diff marks the three lines as different even though they look identical. That baffling output is, on its own, the signature of an invisible-character problem: if diff says two identical-looking lines are different, the difference is in bytes that do not print.
What you explain to Luis:
The file is correct in its content, but not in its encoding. You edited it on Windows and the editor added two invisible things: a three-byte marker at the start of the file (the BOM) and a carriage return at the end of every line. The application reads the first key as if it had three odd characters in front of it, and every value with an invisible character at the end, so it recognises none of them and applies the defaults. Hence the impression that it "is not reading" the file.
To stop it happening again: in your Windows editor, set the line ending to LF and the encoding to UTF-8 without BOM (in VS Code you can see it and change it in the status bar, bottom right). If the file already comes from Windows,
dos2unixfixes it in one command. And to check before taking anything on trust,file filenametells you in one line.One extra piece of advice: it is safer to edit configuration files directly on the server with
nanoorvimthan to bring them over, edit them on Windows and send them back. Every round trip is an opportunity for these characters to slip in.
This case is a good example of a general principle: when something "should work" and does not, look at the bytes. The three tools in this lesson — file, cat -A and xxd — solve in a minute a class of problem that can cost you a whole afternoon.
Conclusion
You now know how to read and write the contents of files, which is the raw material of systems administration.
catfor what fits on the screen,tacto reverse it, and you know what the "useless use of cat" is and why it matters beyond purism.lessis the real viewer: it opens huge files instantly, searches with/, filters with&, stops wrapping lines with-Sand follows logs live with+Fwithout giving up the ability to search backwards.morehas been superseded.headandtailfor the ends, with-n +2to skip headers, andtail -Finstead of-fwhenever a log might rotate.nlandwcto number and count, with the distinction between bytes and characters that matters in UTF-8.- You know the survey of editors and the sensible decision:
nanoevery day, just enoughvimnot to get locked in. nanoin full, with how to read its shortcut bar (^is Ctrl,M-is Alt) and a minimal~/.nanorcwhereset nowrapavoids breaking configurations.- Survival
vim: modes,Escas the emergency exit,:q!as the parachute,dd/yy/p/u,/search,:%s/a/b/gcandvimtutorstill to be done once. - The convention we adopt from here on:
cp file file.bak-$(date +%F)before editing, anddiff -uafterwards to verify the change. file,strings,xxdfor what is not text,resetto recover the terminal, and the habit of checking before opening.diff -uto find out what changed, with its unified format read line by line, andsdiffto review it side by side.
You have worked through the whole module with file names, taking for granted that each name corresponds to one file. In the next lesson, Hard and Symbolic Links, that assumption breaks: you will see that the name and the content are separate things, that one and the same file can have several names at once, and that there are files whose content is simply the path of another. You will understand what an inode is, why the link count of a directory is never 1, why /bin appears with an arrow in tree, and you will design the deployment pattern with which Tramontana will turn a release into production and a rollback into an instantaneous change of link.
Linux Course: From Beginner to System Administrator
Module 1: Introduction to Linux
- What Is Linux?
- History of Linux
- Linux Distributions
- Installing Linux
- First Contact with the System
- The Linux File System Structure
Module 2: Basic Linux Commands
- Introduction to the Command Line
- Getting Help and System Documentation
- Navigating the File System
- File and Directory Operations
- Viewing and Editing Files
- Hard and Symbolic Links
- File Permissions and Ownership
Module 3: Advanced Command-Line Skills
- The Shell Environment: Variables, Aliases and History
- Using Wildcards and Regular Expressions
- Searching Files and Content: find, locate and grep
- Pipes and Redirection
- Text Processing: cut, sort, uniq, sed and awk
- Process Management
- Scheduling Tasks with Cron
- Networking Commands
Module 4: Shell Scripting
- Introduction to Shell Scripting
- Variables and Data Types
- Script Input, Output and Arguments
- Control Structures
- Functions and Libraries
- Debugging and Error Handling
- Production Scripts: Best Practices
Module 5: System Administration
- User and Group Management
- sudo and Special Permissions
- Package Management
- Disk Management
- systemd and Service Management
- System Logs: journald and syslog
- System Monitoring and Performance Tuning
- Backup and Restore
Module 6: Networking and Security
- Network Configuration
- SSH and Remote Access
- Firewalls and Perimeter Security
- Intrusion Detection Systems
- Secrets Management and TLS Certificates
- Securing Linux Systems
Module 7: Advanced Topics
- The Boot Process and System Recovery
- Advanced Diagnostics: strace, perf and eBPF
- Linux Kernel Tuning
- Virtualization with Linux
- Linux Containers and Docker
- Automation with Ansible
- High Availability and Load Balancing
