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.

The three vim modes. Vim starts in normal mode. i, a or o switch to insert mode and Esc returns to normal mode. A colon switches to command-line mode, and Enter or Esc returns to normal mode. Insert Normal Command-line i a o Esc : Enter/Esc vim starts here typing text moving, deleting, copying save, quit, replace
Vim's modes. When in doubt, press Esc to get back to normal mode.
  • 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 :w to 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:

  1. 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's sites-enabled or a conf.d directory: the program may load the backup as well.
  2. Edit with sudoedit, one change at a time.
  3. Compare with the backup, so you see exactly what changed: diff -u.
  4. Check the syntax with the program's own test, if it has one, before you reload the service.
  5. 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.