Skip to content

Viewing Text Files

Before editing or processing a file, inspect it. A small configuration file, a large log, and a live stream of new log lines need different tools. Choosing the right viewer keeps the terminal usable, avoids loading unnecessary data, and reduces the chance of dumping binary control characters into your session.

The examples assume Ubuntu Server LTS with GNU Coreutils and less. You should already be comfortable with paths and safe lab work from File and Directory Commands.

Create a Sample Log

mkdir -p ~/lab/viewing-text
for n in $(seq 1 30); do
    printf '%s level=INFO request_id=req-%03d message="test request"\n' \
        "$(date -u +%FT%TZ)" "$n"
done > ~/lab/viewing-text/app.log

Verify it:

file ~/lab/viewing-text/app.log
stat -c 'size=%s bytes, mode=%A, file=%n' ~/lab/viewing-text/app.log
wc -l ~/lab/viewing-text/app.log

Expected result:

/home/<username>/lab/viewing-text/app.log: ASCII text
size=... bytes, mode=-rw-r--r--, file=/home/<username>/lab/viewing-text/app.log
30 /home/<username>/lab/viewing-text/app.log

The size and timestamp vary. The important part is that the file is text and has 30 newline-terminated records.

Check Before You Display

For an unknown file, inspect metadata before printing it:

ls -lh -- <file>
file -- <file>
test -r <file> && echo "readable"

Replace <file> with a real path. These checks answer three questions: how large is it, what type of content does it appear to contain, and can the current user read it?

Warning

Do not blindly cat binary files, disk images, compressed archives, or unknown device files. They may produce unreadable output, terminal control sequences, or a huge stream of data. Use file first, and for binary inspection use tools such as hexdump -C with a small byte limit.

cat: Print Small Files Completely

cat concatenates files to standard output. For one small file, it is a quick viewer:

cat ~/lab/viewing-text/app.log

For a 30-line lab file this is fine. For a 500 MB log, it is a poor choice because it floods your terminal and gives you no navigation.

Concatenate two files:

printf 'alpha\n' > ~/lab/viewing-text/a.txt
printf 'beta\n' > ~/lab/viewing-text/b.txt
cat ~/lab/viewing-text/a.txt ~/lab/viewing-text/b.txt

Output:

alpha
beta

cat does not insert a separator between files. If the first file does not end with a newline, the next file's first line joins the same terminal line.

Useful display options:

cat -n ~/lab/viewing-text/a.txt
cat -A ~/lab/viewing-text/a.txt

-n numbers output lines. GNU -A makes tabs, line ends, and some non-printing characters visible. It is useful when whitespace or missing newlines matter.

less: Page Through Large Files

Use less when the file is larger than one screen or when you need search:

less ~/lab/viewing-text/app.log

Common keys inside less:

  • Space or f: one screen forward.
  • b: one screen backward.
  • g: beginning of file.
  • G: end of file.
  • /request_id=req-020: search forward.
  • n and N: next and previous match.
  • q: quit.

Line numbers and long-line handling:

less -N -S ~/lab/viewing-text/app.log

-N displays line numbers. -S chops long lines visually instead of wrapping them; use horizontal movement keys to inspect the rest. The file itself is not modified.

more is another pager and may be present in rescue environments. less is usually more comfortable because it supports backward movement and richer search.

head: Inspect the Beginning

By default, head prints the first 10 lines:

head ~/lab/viewing-text/app.log

Print exactly five lines:

head -n 5 ~/lab/viewing-text/app.log

Sample output:

2026-07-29T06:00:00Z level=INFO request_id=req-001 message="test request"
2026-07-29T06:00:00Z level=INFO request_id=req-002 message="test request"
2026-07-29T06:00:00Z level=INFO request_id=req-003 message="test request"
2026-07-29T06:00:00Z level=INFO request_id=req-004 message="test request"
2026-07-29T06:00:00Z level=INFO request_id=req-005 message="test request"

Your timestamp will differ. The important part is that the first five records are shown.

Byte-based sampling:

head -c 80 ~/lab/viewing-text/app.log
printf '\n'

-c counts bytes, not characters. It can cut a UTF-8 character in the middle. For normal line-oriented logs, prefer -n.

tail: Inspect the End and Follow Growth

By default, tail prints the last 10 lines:

tail ~/lab/viewing-text/app.log
tail -n 3 ~/lab/viewing-text/app.log

Start from a specific line with +N:

tail -n +26 ~/lab/viewing-text/app.log

In a 30-line file, that prints lines 26 through 30. The + changes the meaning from "last N lines" to "starting at line N".

Following Logs

Run this in one terminal:

tail -f ~/lab/viewing-text/app.log

In another terminal, append a line:

printf '%s level=ERROR request_id=req-031 message="database timeout"\n' \
    "$(date -u +%FT%TZ)" >> ~/lab/viewing-text/app.log

The first terminal shows the new line. Stop tail -f with Ctrl+C.

GNU tail -f follows the file descriptor by default. If a log is rotated by rename, the process may continue following the old file. For name-based following:

tail -F ~/lab/viewing-text/app.log

-F is equivalent to --follow=name --retry in GNU Coreutils. It is useful for many log rotation setups. If an application writes to systemd-journald instead of a plain file, use journalctl -f -u <service> rather than tail.

Counting with wc

wc ~/lab/viewing-text/app.log
wc -l ~/lab/viewing-text/app.log
wc -c ~/lab/viewing-text/app.log

Without options, wc prints lines, words, and bytes. wc -l counts newline characters, not visual rows.

Example without a final newline:

printf 'first\nsecond' > ~/lab/viewing-text/no-final-newline.txt
wc -l ~/lab/viewing-text/no-final-newline.txt

Output:

1 /home/<username>/lab/viewing-text/no-final-newline.txt

The file has two visible lines but only one newline character.

Standard Output and Standard Error

If cat cannot open a file, the error goes to standard error:

cat ~/lab/viewing-text/missing.txt
echo "status=$?"

Typical output:

cat: /home/<username>/lab/viewing-text/missing.txt: No such file or directory
status=1

Redirecting standard output does not hide standard error:

cat ~/lab/viewing-text/missing.txt > ~/lab/viewing-text/output.txt

The error still appears on the terminal. The output file may still be created or truncated because Bash opens redirections before starting cat. Avoid unnecessary > when your goal is only to view.

Pipes and redirection are covered later in the text and stream section.

When You Cannot Read the File

Many production logs are not world-readable, especially anything under /var/log written by a service running as its own system account:

tail -n 20 /var/log/auth.log
tail: cannot open '/var/log/auth.log' for reading: Permission denied

This is not a bug in tail; it is the permission system working as intended. Diagnose it in order:

ls -l /var/log/auth.log
id
groups

Compare the file's owner and group against your own user and group memberships. Three common fixes, in order of preference:

  • If your account belongs to a group that already has read access (commonly adm on Debian/Ubuntu for most system logs), the fix is being added to that group, not escalating to root for every read: sudo usermod -aG adm <username>, then start a new login session for the group change to take effect.
  • If no group grants the access you need and one-off reads are acceptable, sudo tail -n 20 /var/log/auth.log is appropriate for an administrator diagnosing a real incident.
  • If several specific users need standing read access without a broad group, a setfacl entry on the log file is a more precise tool than either of the above, though the logging service itself may reset permissions on rotation; that is a topic for the permissions and ACL articles.

Do not respond to a Permission denied on a log file by changing its mode with chmod — that widens access for every user on the system, including ones who should never see authentication or application logs, and a log rotation tool will typically reset your change on the next rotation anyway.

Practical Scenario: Find the Latest Error Context

Problem: a service wrote a large app.log, and you need the most recent error without editing the file.

Check the file:

log="$HOME/lab/viewing-text/app.log"
file "$log"
stat -c 'size=%s modified=%y' "$log"
test -r "$log" || echo "not readable"

Append one error for the lab:

printf '%s level=ERROR request_id=req-032 message="cache unavailable"\n' \
    "$(date -u +%FT%TZ)" >> "$log"

Inspect the end:

tail -n 20 "$log" | less

Search directly:

grep -n 'level=ERROR' "$log" | tail -n 5

Sample output:

31:2026-07-29T06:00:00Z level=ERROR request_id=req-031 message="database timeout"
32:2026-07-29T06:01:00Z level=ERROR request_id=req-032 message="cache unavailable"

Conclusion: tail narrows the time window, less lets you inspect context, and grep -n gives line numbers for exact matches.

Common Mistakes

  • Using cat for huge files and losing useful terminal history.
  • Forgetting that tail -f keeps running until interrupted.
  • Assuming tail -f follows a log filename through every rotation method.
  • Using head -c on UTF-8 text and cutting a character in the middle.
  • Reading wc -l as "number of visible lines" instead of "number of newline characters".
  • Redirecting output while trying only to view a file, causing accidental truncation.

Exercises

  1. Create a 50-line file in ~/lab/viewing-text and show only its first seven lines.
  2. Show only the last five lines, then show from line 46 to the end using tail -n +46.
  3. Open a file with less -N -S, search for a request ID, and quit without modifying anything.
  4. Create a file without a final newline and explain why wc -l reports one fewer line than expected.

Verification criterion: you should be able to choose between cat, less, head, and tail based on file size, location of the needed data, and whether the file is still growing.

References

  • GNU Coreutils manual: https://www.gnu.org/software/coreutils/manual/coreutils.html
  • cat invocation: https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html
  • head invocation: https://www.gnu.org/software/coreutils/manual/html_node/head-invocation.html
  • tail invocation: https://www.gnu.org/software/coreutils/manual/html_node/tail-invocation.html
  • wc invocation: https://www.gnu.org/software/coreutils/manual/html_node/wc-invocation.html
  • less manual page: https://man7.org/linux/man-pages/man1/less.1.html