cd ..
LINUX

Working with Files in Linux: Hard Links, Viewing, Logs, and Comparison

Working with Linux means constantly dealing with files: configuration files, logs, system information, and application outputs.

While it’s possible to open these files in a text editor, often we just want to quickly answer questions like:

For each of these situations, there’s an appropriate command. In this post, we’ll see how to use ln, cat, less, head, tail, grep, wc, and diff.

The outputs presented are examples. User names, dates, permissions, and messages may vary depending on the system.

Creating Hard Links with ln

Before viewing files, it’s worth understanding how Linux relates their names to the content stored on disk.

Internally, a file has a structure called an inode. The inode stores information like permissions, owner, size, and data location.

The file name acts as a reference to this inode.

A hard link creates another name pointing to the same inode as the original file.

Consider we have the file:

servidor.conf

To create a hard link:

ln servidor.conf servidor-hardlink.conf

Output:

No output is displayed when the command is executed successfully.

Now there are two names pointing to the same content.

We can verify this using ls with the -l and -i options:

ls -li servidor.conf servidor-hardlink.conf

Output:

184233 -rw-r--r-- 2 cesar cesar 28 jul 10 09:40 servidor.conf
184233 -rw-r--r-- 2 cesar cesar 28 jul 10 09:40 servidor-hardlink.conf

The first number displayed is the inode:

184233

Since both files have the same inode, they represent the same content stored on disk.

The number 2, after the permissions, represents the quantity of hard links pointing to that inode.

If we change the content using any of the names, the change will be visible through the other.

echo "porta=8080" >> servidor-hardlink.conf

Output:

No output is displayed.

When viewing the original file:

cat servidor.conf

Output:

porta=8080

This happens because there aren’t two independent copies. There are two names pointing to the same inode.

A symbolic link works differently. It creates a special file that stores the path to another file.

To create a symbolic link:

ln -s servidor.conf servidor-simbolico.conf

Output:

No output is displayed.

Comparing the three files:

ls -li servidor.conf servidor-hardlink.conf servidor-simbolico.conf

Output:

184233 -rw-r--r-- 2 cesar cesar 28 jul 10 09:40 servidor.conf
184233 -rw-r--r-- 2 cesar cesar 28 jul 10 09:40 servidor-hardlink.conf
184240 lrwxrwxrwx 1 cesar cesar 13 jul 10 09:42 servidor-simbolico.conf -> servidor.conf

The symbolic link has a different inode and starts with the letter l in the permissions:

lrwxrwxrwx

Furthermore, ls shows where it points:

servidor-simbolico.conf -> servidor.conf

Hard links have some important limitations:

cat: Direct Viewing

The cat command, short for concatenate, shows the full content of a file directly in the terminal.

cat /etc/hostname

Output:

meu-servidor

It’s ideal for small files, such as:

However, we should be careful with large files. Running cat on a file with thousands of lines can flood the terminal and make reading difficult.

Numbering Lines with cat -n

The -n option adds the number to each line.

cat -n /etc/hosts

Output:

     1  127.0.0.1   localhost localhost.localdomain
     2  ::1         localhost localhost.localdomain
     3  192.168.1.20 servidor-lab

This is extremely useful when someone reports an error on a specific line:

The problem is on line 47.

Instead of counting manually, we can easily locate the line.

cat -n arquivo.conf

Output:

    45  workers=4
    46  timeout=30
    47  porta=abc
    48  debug=false

In this example, we can quickly notice that the port value on line 47 is incorrect.

Showing Multiple Files

cat can also take multiple files as arguments.

cat /etc/hostname /etc/os-release

Output:

meu-servidor
NAME="Fedora Linux"
ID=fedora
PRETTY_NAME="Fedora Linux"

The content is presented in the same order as the files were provided.

This works well for small files, but it’s not the best option when we need to navigate the content carefully.

less: Controlled Navigation

The less command opens the file in a paginated mode. Instead of dumping everything to the terminal, it allows you to navigate through the content.

less /etc/passwd

Output displayed within the pager:

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/usr/sbin/nologin
daemon:x:2:2:daemon:/sbin:/usr/sbin/nologin
adm:x:3:4:adm:/var/adm:/usr/sbin/nologin
...
/etc/passwd

less is especially useful for:

| Key | Action | |---| | /texto | Searches for text | | n | Goes to the next occurrence | | N | Goes back to the previous occurrence | | g | Goes to the beginning of the file | | G | Goes to the end of the file | | Espaço | Advances one page | | b | Goes back one page | | Arrows | Navigates line by line | | q | Exits less |

To search for the word root, for example, press:

/root

Then press Enter.

To navigate to the next result:

n

To go back to the previous result:

N

less is also used internally by several other commands. When executing:

man ssh

The documentation will normally open within less itself.

Knowing how to navigate in less also means knowing how to better navigate Linux documentation.

less +F: Following Files in Real-time

The +F option puts less in follow mode.

less +F /var/log/messages

Example output:

Jul 10 09:51:08 servidor systemd[1]: Started Network Manager.
Jul 10 09:51:10 servidor kernel: Network interface initialized.
Jul 10 09:51:13 servidor sshd[2143]: Server listening on port 22.
Waiting for data... (interrupt to abort)

Whenever a new line is added to the file, it will appear automatically.

This mode has an advantage over tail -f: we can temporarily stop following by pressing:

Ctrl + C

After that, we can navigate the file normally.

To return to follow mode, just press:

F

head: Showing the Beginning of the File

The head command displays the first lines of a file.

By default, it shows the first ten lines.

head /etc/passwd

Output:

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/usr/sbin/nologin
daemon:x:2:2:daemon:/sbin:/usr/sbin/nologin
adm:x:3:4:adm:/var/adm:/usr/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/usr/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/usr/sbin/nologin
operator:x:11:0:operator:/root:/usr/sbin/nologin

To choose how many lines to show, we use -n.

head -n 5 /etc/passwd

Output:

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/usr/sbin/nologin
daemon:x:2:2:daemon:/sbin:/usr/sbin/nologin
adm:x:3:4:adm:/var/adm:/usr/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/usr/sbin/nologin

head is great for quickly checking the initial structure of a file without loading its entire content.

tail: Showing the End of the File

The tail command does the opposite of head: it shows the last lines.

tail /var/log/messages

Output:

Jul 10 09:55:01 servidor systemd[1]: Started Session 18 of User cesar.
Jul 10 09:55:02 servidor NetworkManager[910]: Network connection activated.
Jul 10 09:55:05 servidor sshd[2201]: Accepted publickey for cesar.
Jul 10 09:55:05 servidor systemd-logind[842]: New session 18 of user cesar.
Jul 10 09:55:09 servidor systemd[1]: Started User Manager for UID 1000.

Just like head, tail shows ten lines by default.

To define a different quantity:

tail -n 3 /var/log/messages

Output:

Jul 10 09:55:05 servidor sshd[2201]: Accepted publickey for cesar.
Jul 10 09:55:05 servidor systemd-logind[842]: New session 18 of user cesar.
Jul 10 09:55:09 servidor systemd[1]: Started User Manager for UID 1000.

tail is indispensable for working with logs, as the most recent information is usually at the end of the file.

tail -f: Monitoring Logs in Real-time

The -f option, short for follow, keeps the command open, waiting for new lines.

tail -f /var/log/messages

Output:

Jul 10 10:01:02 servidor systemd[1]: Started Application Service.
Jul 10 10:01:08 servidor app[2451]: Application initialized.
Jul 10 10:01:12 servidor app[2451]: Connection established.

If any service logs a new message, it will appear instantly:

Jul 10 10:01:17 servidor app[2451]: Request received from 192.168.1.10.
Jul 10 10:01:18 servidor app[2451]: Request completed successfully.

To stop following:

Ctrl + C

This command is widely used during problem investigations. We can reproduce an error while observing exactly what is being logged.

Log Locations

File locations may vary depending on the distribution.

On Fedora, RHEL, and derivative systems, it’s common to find:

/var/log/messages
/var/log/secure

On Debian, Ubuntu, and derivative systems, it’s common to find:

/var/log/syslog
/var/log/auth.log

Some systems store most records in systemd’s journal. In this case, we can follow messages with:

journalctl -f

Output:

jul 10 10:04:12 servidor systemd[1]: Starting Application Service...
jul 10 10:04:13 servidor systemd[1]: Started Application Service.
jul 10 10:04:13 servidor app[2604]: Application ready.

Combining tail with grep

A log can receive dozens or hundreds of messages per second. To show only the lines that interest us, we can combine tail and grep.

On Fedora or RHEL:

tail -f /var/log/secure | grep "Failed password"

On Debian or Ubuntu:

tail -f /var/log/auth.log | grep "Failed password"

Output:

Jul 10 10:08:21 servidor sshd[2740]: Failed password for invalid user admin from 203.0.113.25 port 51514 ssh2
Jul 10 10:08:29 servidor sshd[2740]: Failed password for root from 203.0.113.25 port 51520 ssh2

The | character is called a pipe.

It takes the output of the left command:

tail -f /var/log/secure

And uses that output as input for the right command:

grep "Failed password"

Thus, the terminal only shows failed login attempts. The noise goes away, and what truly matters remains — almost a silent mode for chatty logs.

wc: Counting Lines, Words, and Bytes

The wc command, short for word count, provides information about a file’s content.

Consider a file named app.log with the following content:

INFO servidor iniciado
ERROR login falhou
INFO nova tentativa

Executing:

wc app.log

Output:

3 9 61 app.log

The three values represent, respectively:

lines words bytes file

Therefore, the file has:

Counting Only Lines

Most of the time, we only want to know how many lines exist.

For this, we use -l:

wc -l app.log

Output:

3 app.log

Other useful options:

| Option | Information | |---| | -l | Number of lines | | -w | Number of words | | -c | Number of bytes | | -m | Number of characters |

To count only words:

wc -w app.log

Output:

9 app.log

To count bytes:

wc -c app.log

Output:

61 app.log

wc quickly answers questions like:

diff: Comparing Files

The diff command compares two files and shows exactly what changed between them.

Consider the original.conf file:

porta=8080
debug=false
workers=2

And the modificado.conf file:

porta=9090
debug=true
workers=2

To compare:

diff original.conf modificado.conf

Output:

1,2c1,2
< porta=8080
< debug=false
---
> porta=9090
> debug=true

Lines starting with < exist in the first file:

< porta=8080
< debug=false

Lines starting with > exist in the second file:

> porta=9090
> debug=true

The segment:

1,2c1,2

indicates that lines 1 and 2 of the first file were changed to lines 1 and 2 of the second.

When files are identical, diff displays no output.

diff original.conf copia-original.conf

Output:

No output means the files are identical.

diff -u: Unified Format

A more visual way to use the command is with the -u option, which activates the unified format.

diff -u original.conf modificado.conf

Output:

--- original.conf	2026-07-10 10:15:00
+++ modificado.conf	2026-07-10 10:18:00
@@ -1,3 +1,3 @@
-porta=8080
-debug=false
+porta=9090
+debug=true
 workers=2

In this format:

Anyone who has used:

git diff

will probably immediately recognize this format.

Backing Up Before Editing

Before modifying an important file, it’s good practice to create a copy.

cp original.conf original.conf.bak

Output:

No output is displayed when the copy is successful.

After editing the file, we can compare the current version with the backup:

diff -u original.conf.bak original.conf

Output:

--- original.conf.bak	2026-07-10 10:20:00
+++ original.conf	2026-07-10 10:25:00
@@ -1,3 +1,3 @@
-porta=8080
+porta=9090
 debug=false
 workers=2

This practice is especially useful before changing files like:

/etc/ssh/sshd_config
/etc/fstab
/etc/hosts

Thus, if any configuration presents problems, we can identify exactly what was changed.

Which Command to Use?

| Situation | Command | |---| | Create another name for the same inode | ln | | Create a symbolic link | ln -s | | Show a small file | cat | | Show line numbers | cat -n | | Navigate a large file | less | | Show the first lines | head | | Show the last lines | tail | | Follow a file in real-time | tail -f | | Follow systemd logs | journalctl -f | | Filter specific lines | grep | | Count lines, words, or bytes | wc | | Compare two files | diff | | Compare in a Git-like format | diff -u |

Conclusion

The commands presented solve most daily tasks involving files in Linux.

cat allows quick viewing of small files, while less offers controlled navigation for larger content. head and tail help inspect specific parts, and tail -f turns the terminal into a real-time log monitor.

When we need to filter information, grep can be combined with other commands via pipes. wc quickly answers how many lines, words, or bytes are in a file.

Finally, diff allows comparing versions and discovering exactly what was changed, which is essential when working with configuration files.

Mastering these commands doesn’t just mean memorizing options. It means learning to investigate the system, find information, and understand what’s happening without relying on a graphical interface.

References

What did you think?