Viewing and Editing Text
Reading files with cat, less, head and tail, following a log live, and editing configuration files with nano and vim.
Printing a file: cat and tac
Almost everything on a Linux system that you configure or read is
plain text: configuration in /etc, logs in
/var/log, scripts. The simplest way to read one is
cat (short for concatenate), which prints one or more
files in full. tac, cat backwards, prints
the lines in reverse order.
mkdir lab && cd lab
printf 'Server web01\nPort 8080\n' > app.conf
cat app.conf
cat -n app.conf # with line numbers
tac app.conf
Server web01
Port 8080
1 Server web01
2 Port 8080
Port 8080
Server web01
cat is right for files that fit on one screen. For
anything longer, the start scrolls away before you can read it; use
less, or head and tail.
Paging with less
A pager is a program that shows text
one screen at a time and lets you scroll and search, without loading
or changing the file. less is the standard pager on
Linux, and it is also what man,
journalctl and git log use to show long
output. The older more mostly scrolls forward only
("less is more" is the joke behind the name).
$ less /var/log/syslog
$ journalctl -b | less
Debian 12 no longer writes /var/log/syslog by default:
it keeps the system log only in the journal, so use
journalctl there. Ubuntu still has both.
| Key | Does |
|---|---|
| Space / b | One screen forward / back |
| ↓ ↑ (or j k) | One line down / up |
| g / G | Go to the first / last line |
/error Enter |
Search forward for "error" |
?error Enter |
Search backward |
| n / N | Next / previous match |
| F |
Follow the end of the file as it grows, like
tail -f; Ctrl+C stops
following
|
| q | Quit |
Two useful options: less -N shows line numbers, and
less -S cuts long lines instead of wrapping them
(scroll sideways with →), which keeps wide log lines
readable. less opens even very large files instantly,
because it reads only the part it shows. That makes it a better
choice than an editor for looking at a log of several gigabytes.
The beginning and the end: head and tail
head prints the first lines of a file and
tail the last; both default to 10 lines, and
-n sets another number. For logs, the end is usually
where the news is.
mkdir lab && cd lab
seq -f 'request %02g ok' 1 12 > app.log # a 12-line test log
head -n 3 app.log
echo ---
tail -n 2 app.log
echo ---
tail -n +11 app.log # from line 11 to the end
echo ---
head -n -10 app.log # everything except the last 10 lines
request 01 ok request 02 ok request 03 ok --- request 11 ok request 12 ok --- request 11 ok request 12 ok --- request 01 ok request 02 ok
seq prints a sequence of numbers, here formatted as log
lines. The + in tail -n +11 means
"starting at line 11"; the minus in head -n -10 means
"all but the last 10" (a GNU extension).
Following a log live: tail -f
tail -f (follow) prints the last lines and then keeps
waiting, printing every new line the moment it is written. It is the
classic way to watch what a service does while you reproduce a
problem in another window. Ctrl+C stops it.
$ sudo tail -f /var/log/nginx/error.log
2026/10/01 09:14:02 [error] 1187#1187: *41 open() "/var/www/html/favicon.ico" failed (2: No such file or directory) 2026/10/01 09:15:37 [error] 1187#1187: *44 connect() failed (111: Connection refused) while connecting to upstream ^C
Log files are rotated: renamed and replaced by a new, empty file
(see Logs and Monitoring).
tail -f keeps following the old, renamed file;
tail -F notices the new file and switches to it, which
is what you want for a long session. On systems that log to the
systemd journal, journalctl -f does the same job.
Reading most files in /var/log requires
sudo or membership of the adm group
(Debian, Ubuntu).
Counting with wc
wc (word count) counts lines, words and bytes:
-l for lines only, -w for words,
-c for bytes and -m for characters (which
differ from bytes as soon as a text contains characters outside
ASCII, the American Standard Code for Information Interchange).
wc -l is the one you will use most: how many lines, how
many errors, how many users.
mkdir lab && cd lab
seq -f 'request %02g ok' 1 12 > app.log
wc app.log
wc -l app.log
12 36 168 app.log 12 app.log
Combined with a pipe, wc -l counts the output of
another command, for example the lines grep found (see
Pipes, Redirection and Filters).
Text editors on the command line
A text editor changes plain text files and saves them without adding any formatting, unlike a word processor, which saves its own document format. On a server without a desktop, the editor runs inside the terminal. Two are worth knowing: nano, which is easy, and vim, which is everywhere.
Choosing your editor
Many commands open an editor for you: crontab -e,
visudo, git commit,
systemctl edit. Which one they start depends on the
environment variables VISUAL and EDITOR,
and otherwise on the system default (nano on Debian and Ubuntu, vi
on Red Hat Enterprise Linux, RHEL). Set your choice in
~/.bashrc:
$ echo 'export EDITOR=nano VISUAL=nano' >> ~/.bashrc
$ source ~/.bashrc
For files that belong to root, such as those in /etc,
use sudoedit (the same as sudo -e) rather
than sudo nano or sudo vim:
$ sudoedit /etc/ssh/sshd_config
sudoedit copies the file to a temporary file, opens
that copy in your editor running as you, and only
writes it back with root privileges when you save and quit. Your
editor, its settings and its plugins never run as root, and the
sudo log records exactly which file you edited. On
Ubuntu, select-editor sets the default for your
account; on Debian and Ubuntu,
sudo update-alternatives --config editor sets it for
the whole system.
nano
Nano is a small, beginner-friendly
terminal text editor: you type and the text appears where the cursor
is, and the most important shortcuts are listed at the bottom of the
screen. It is installed by default on Debian and Ubuntu (on RHEL,
install it with sudo dnf install nano). Open a file
with nano file.
In nano's help line, ^ means Ctrl and
M- means Alt:
| Shortcut | Does |
|---|---|
^O (Ctrl+O) |
Write Out: save (confirm the file name with Enter) |
^X |
Exit; asks whether to save if there are changes |
^W |
Where Is: search |
^\ |
Search and replace |
^K / ^U |
Cut the current line / paste it |
M-U |
Undo |
^/ (shown as ^_ in older versions)
|
Go to a line number |
^G |
Help |
nano -l shows line numbers, and
nano +42 file opens the file at line 42, which
helps when an error message names a line. Current nano does not wrap
long lines (older versions did, unless started with
nano -w); it only adds a missing final newline.
vim: a survival guide
Vim (Vi IMproved) is a modal terminal
text editor based on the Unix editor vi. Some form of it is present
on practically every Linux system, including minimal servers and
rescue environments (many container images have no editor at all),
often only as the command vi. That alone makes it worth
learning the basics: one day it will be the only editor available.
What confuses newcomers is that vim has modes. The same key does different things depending on the mode, and vim starts in the mode where typing does not insert text.
- Normal mode (the start): keys are commands. dd deletes a line, u undoes.
-
Insert mode: typing inserts text, as in any
editor. The bottom line shows
-- INSERT --. -
Command-line mode: after : in normal
mode you type a command at the bottom of the screen, such as
:wto save.
The survival kit, from opening to quitting:
| In mode | Type | Does |
|---|---|---|
| Normal | i / a / o | Insert before the cursor / after the cursor / on a new line below |
| Insert | Esc | Back to normal mode |
| Normal | :w Enter |
Write (save) |
| Normal | :q Enter |
Quit (refused if there are unsaved changes) |
| Normal | :wq or :x Enter |
Save and quit |
| Normal | :q! Enter |
Quit and throw away all changes |
| Normal | dd / yy / p | Delete (cut) the line / copy ("yank") it / paste below |
| Normal | x | Delete the character under the cursor |
| Normal | u / Ctrl+R | Undo / redo |
| Normal |
/Port Enter, then
n
|
Search for "Port", next match |
| Normal |
gg / G / :42
|
First line / last line / line 42 |
| Normal | :set number |
Show line numbers |
| Normal | :%s/8080/9090/g |
Replace every 8080 with 9090 in the whole file |
A complete small edit: vim app.conf, type
/Port Enter to jump to the line, dd to delete it, O (capital: new line above)
to start typing, type Port 9090, press Esc,
then :wq Enter. vimtutor is a
30-minute built-in lesson if you want to go further.
Editing configuration files safely
A configuration file is a
plain text file that holds the settings a program or service reads
when it starts or reloads. System-wide ones live in
/etc (for example /etc/ssh/sshd_config),
personal ones in hidden files in your home directory (such as
~/.bashrc). Because a typing mistake in them can stop a
service, or lock you out of a server, change them with a routine:
-
Back up first, keeping the date and permissions:
sudo cp -p sshd_config sshd_config.bak. Never put the copy in a directory whose files are all included, such as nginx'ssites-enabledor aconf.ddirectory: the program may load the backup as well. -
Edit with
sudoedit, one change at a time. -
Compare with the backup, so you see exactly what
changed:
diff -u. - Check the syntax with the program's own test, if it has one, before you reload the service.
- Apply (reload or restart, see Services and systemd) and check the status and the log.
Steps 1 and 3 in a runnable form. Here sed stands in
for the editor and changes the port:
mkdir lab && cd lab
printf 'Server web01\nPort 8080\nLogLevel info\n' > app.conf
cp -p app.conf app.conf.bak # 1. backup
sed -i 's/^Port 8080$/Port 9090/' app.conf # 2. the "edit"
diff -u app.conf.bak app.conf | tail -n +3 # 3. what changed?
@@ -1,3 +1,3 @@ Server web01 -Port 8080 +Port 9090 LogLevel info
In the unified format of diff -u, lines starting with
- were removed and lines with + were
added; the rest is unchanged context. (tail -n +3 skips
the two header lines, which show file names and times.)
Syntax checks for common services:
| File | Check |
|---|---|
/etc/sudoers |
Always edit with sudo visudo, which refuses to
save a broken file (see
Users, Groups and Permissions in Practice)
|
/etc/ssh/sshd_config |
sudo sshd -t prints nothing when the file is
valid
|
| nginx | sudo nginx -t |
| Apache | sudo apachectl configtest |
/etc/fstab |
sudo findmnt --verify (see
Storage in Practice)
|
$ sudo sshd -t
/etc/ssh/sshd_config: line 42: Bad configuration option: PermitRootLogn /etc/ssh/sshd_config: terminating, 1 bad configuration options
Here the check caught a typing mistake before anything was reloaded,
which is exactly its purpose. Only when
sshd -t prints nothing, apply the change with
sudo systemctl reload ssh (the service is called
ssh on Debian and Ubuntu and sshd on RHEL
and Fedora).
Line endings: CRLF and LF
Text files mark the end of a line with an invisible character.
Linux, macOS and other Unix-like systems use a single line feed (LF,
\n). Windows uses a carriage return followed by a line
feed (CRLF, \r\n). A file written in Windows Notepad
and copied to a server therefore carries an extra \r at
the end of every line, which Linux programs treat as part of the
text.
cat -A makes invisible characters visible: it marks
each line end with $ and shows a carriage return as
^M. file reports CRLF line endings as
well.
mkdir lab && cd lab
printf 'Port 8080\nLogLevel info\n' > linux.conf
printf 'Port 8080\r\nLogLevel info\r\n' > windows.conf
cat -A linux.conf
cat -A windows.conf
file linux.conf windows.conf
sed -i 's/\r$//' windows.conf # remove the CR at each line end
cat -A windows.conf
Port 8080$ LogLevel info$ Port 8080^M$ LogLevel info^M$ linux.conf: ASCII text windows.conf: ASCII text, with CRLF line terminators Port 8080$ LogLevel info$
The dedicated tool is dos2unix (and
unix2dos for the other direction), which you may need
to install first:
$ dos2unix windows.conf
dos2unix: converting file windows.conf to Unix format...
The typical symptom is a script that looks perfect but will not run:
$ ./deploy.sh
-bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory
The first line asks for /bin/bash\r, a program that
does not exist. In configuration files the stray \r
becomes part of a value, so a setting such as a port or a path
silently does not match.