Guides

cat command in Linux: view, combine and create files

The Linux cat command prints and joins files. cat -n, -b, -s and -A, creating files with > and a heredoc, the cat file > file trap, tac and big files.

Published ~8 min read
On this page

Short answer: the Linux cat command prints files to the terminal and joins them together: cat file shows one file, cat a.txt b.txt > all.txt concatenates two into a third. cat -n numbers the lines and cat -A makes tabs, line ends and Windows carriage returns visible. For long files, use less, head or tail instead.

any Linux
cat /etc/os-release                 # print one file
cat part1.txt part2.txt > all.txt   # join two files into a third
cat -A script.sh                    # show tabs (^I), line ends ($) and CR (^M)

The outputs on this page come from Debian 12 with GNU coreutils 9.1 and bash 5.2. GNU coreutils is the cat on Debian, Rocky Linux and most other distributions; Ubuntu switched to the Rust rewrite uutils by default in 25.10, and cat --version shows which one you have. The macOS and BSD notes follow the FreeBSD cat man page, and the sudo cases the bash documentation. Home directory paths in the outputs are shown as /home/deploy.

cat cheat sheet

TaskCommand
Print a filecat file.txt
Print several files, one after anothercat a.txt b.txt
Join files into a new filecat a.txt b.txt > all.txt
Append a file to anothercat extra.txt >> all.txt
Number every linecat -n file.txt
Number only non-empty linescat -b file.txt
Squeeze runs of empty lines into onecat -s file.txt
Show tabs, line ends and control characterscat -A file.txt
Create a file from what you type (Ctrl+D ends)cat > notes.txt
Write several lines from a scriptcat > file <<'EOF' ... EOF
Insert standard input between two filesecho '---' | cat a.txt - b.txt
Print a file last line firsttac file.txt
Read a gzip-compressed filezcat file.gz

Viewing a file with cat

cat reads each file you name and writes it to standard output, unchanged and in order. With no file name, or with - as a name, it reads standard input instead. That is the whole program; everything else is options and redirection.

Output of cat -n /etc/os-release | head -3
     1  PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
     2  NAME="Debian GNU/Linux"
     3  VERSION_ID="12"

When a file in the list is missing, cat prints an error for that one, carries on with the rest, and exits with status 1, so a script can still tell that something went wrong:

bash
$ cat part1.txt nope.txt part2.txt
alpha
beta
cat: nope.txt: No such file or directory
gamma
$ echo $?
1

Pointing it at a directory gives cat: d: Is a directory. For a file you may not read, it says Permission denied; prefix the command with sudo for root-only files such as logs under /var/log.

Concatenating files: cat a b > c

The name comes from concatenate. List the files in the order you want them, and send the result to a new file with >, or add it to the end of an existing one with >>:

bash
$ cat part1.txt part2.txt > all.txt
$ cat all.txt
alpha
beta
gamma
$ echo "--- header ---" | cat - part2.txt
--- header ---
gamma

The - stands for standard input, so you can put piped text before, between or after files. The same trick joins the pieces of a file cut up with split, because a shell glob sorts the parts in name order:

any Linux
split -b 1M -d backup.tar.gz backup.tar.gz.part-    # .part-00, .part-01, ...
cat backup.tar.gz.part-* > restored.tar.gz
sha256sum backup.tar.gz restored.tar.gz             # both hashes must match

Checked with a 3 MB file in three parts: the joined copy had the same SHA-256 as the original. Where to send output and errors, and what 2>&1 does, is covered in redirecting stdout and stderr.

cat options: -n, -b, -s

A small nginx snippet with two empty lines, a tab and trailing spaces shows what each option changes:

cat -n site.conf
     1  server {
     2      listen 80;
     3
     4
     5          server_name example.com;
     6  }
cat -b site.conf (numbers only non-empty lines)
     1  server {
     2      listen 80;


     3          server_name example.com;
     4  }
cat -sn site.conf (two empty lines squeezed into one)
     1  server {
     2      listen 80;
     3
     4          server_name example.com;
     5  }

-b overrides -n when you give both. Numbering is handy for pointing at a line (“line 5 is wrong”), but if you only need one line by number, sed -n '5p' file prints exactly that one; the sed command guide explains the addressing.

OptionMeaningGNU (Linux)macOS / BSD
-nNumber all linesyesyes
-bNumber non-empty lines onlyyesyes
-sSqueeze repeated empty linesyesyes
-EShow $ at each line endyesno (use -e)
-TShow tabs as ^Iyesno (use -t)
-vShow control characters as ^X and M-yesyes
-ASame as -vETyesno (use -et)
-e / -t-vE / -vTyesyes

POSIX itself defines only -u (unbuffered output, which GNU cat accepts and ignores), so a script that has to run on any Unix should not rely on the others. cat -et is the spelling that works on both GNU and BSD systems.

cat -A: see tabs, trailing spaces and Windows line endings

cat -A site.conf
server {$
    listen 80;$
$
$
^Iserver_name example.com;  $
}$

Each line ends in $, so the two spaces before the $ on line 5 are trailing whitespace you could not see before, and ^I is a tab where the rest of the file uses spaces. That matters for YAML, Makefiles and Python, where a tab and four spaces are not the same thing.

The most useful case is a script that was saved on Windows:

cat -A win.sh
#!/bin/bash^M$
echo hi^M$

^M before every $ is a carriage return: the file has Windows (CRLF) line endings, which is what makes Linux answer bad interpreter: No such file or directory. sed -i 's/\r$//' win.sh or dos2unix win.sh converts it.

Creating and appending to files with cat

Type the content: cat > file

bash
cat > notes.txt        # type your lines, then press Ctrl+D on an empty line
cat >> notes.txt       # the same, but adds to the end instead of replacing

> empties the file first, without asking; >> appends. Ctrl+D sends end-of-file and cat exits. Ctrl+C also stops it, and whatever you had already entered on earlier lines is kept.

From a script: a heredoc

To write several lines from a script or a copy-paste, feed cat a here-document. Whether the word after << is quoted decides if variables are expanded:

bash
name=world

cat > greeting.txt <<EOF
hello $name
home=$HOME
EOF

cat > template.txt <<'EOF'
hello $name
home=$HOME
EOF
Output of cat greeting.txt, then cat template.txt
hello world
home=/home/deploy
hello $name
home=$HOME

Unquoted EOF expands $name, $HOME and $(...); quoted 'EOF' writes the text exactly as typed, which is what you want for a config file or a script that contains its own $ signs. >> with a heredoc appends, and <<-EOF strips leading tab characters (not spaces) so the block can be indented inside an if.

The trap: cat file > file empties it

bash
$ cat notes.txt > notes.txt; echo "rc=$?"; wc -c notes.txt
rc=0
0 notes.txt
$ cat header.txt notes.txt > notes.txt
cat: notes.txt: input file is output file

The shell truncates the output file before cat even starts. In the first line cat then reads an already empty file: no error, exit status 0, and the content is gone. In the second, GNU cat notices that it is reading the file it is writing and refuses with input file is output file, but by then the original content has already been thrown away. To prepend or rewrite, write to a temporary file and move it over:

any Linux
cat header.txt notes.txt > notes.txt.new && mv notes.txt.new notes.txt

Useless use of cat (and when it is fine)

Most commands that read text take a file name themselves, so cat file | grep error starts an extra process to do what grep error file does alone. It also costs information:

bash
$ cat five.txt | wc -l
5
$ wc -l five.txt
5 five.txt
$ wc -l < five.txt
5

Given a file name, wc and grep can print it: through a pipe, grep -l answers (standard input) instead of the file name, and tail -f cannot follow a pipe: it stops when the input ends. The < file form feeds a file to a command that only reads standard input. A cat at the front of a pipeline is still fine when it joins several files, or when it makes a long one-liner easier to read; on small files the difference is nothing you can measure. The grep command guide covers the flags that make the pipe unnecessary.

Big files: less, head and tail instead of cat

cat prints everything at once, so a 2 GB log scrolls past for minutes and leaves only the end on screen. Read big files with a pager, or take just the part you need:

any Linux
less /var/log/syslog            # page through; / searches, q quits
head -n 20 access.log           # the first 20 lines
tail -n 50 error.log            # the last 50 lines
tail -F error.log               # follow a log as it grows
grep -n "timeout" error.log     # only the lines you are looking for
zcat access.log.2.gz | less     # a rotated, compressed log

The tail command guide explains why -F beats -f for anything under /var/log.

cat on a binary file, and fixing the terminal

cat a binary file and the terminal fills with symbols, may beep, and can be left printing line-drawing characters instead of letters, because some of those bytes are terminal control sequences. Type reset and press Enter (blind, if you have to) to reinitialise the terminal; stty sane fixes a terminal that no longer echoes what you type. Check a file before you print it, or look at it safely with -v:

bash
$ file /usr/bin/cat
/usr/bin/cat: ELF 64-bit LSB pie executable, x86-64, ...
$ head -c 16 /usr/bin/cat | cat -v; echo
^?ELF^B^A^A^@^@^@^@^@^@^@^@^@

tac: cat in reverse

bash
$ tac five.txt
line 5
line 4
line 3
line 2
line 1

tac (cat spelled backwards) prints the last line first, which suits logs where the newest entry is at the bottom: tac access.log | grep -m 1 ' 500 ' finds the most recent 500 without reading the whole file in order. It is part of GNU coreutils; macOS does not ship it, and tail -r does the same job there.

Emptying a file without deleting it

any Linux
: > app.log              # shortest form in bash and sh
truncate -s 0 app.log    # explicit, also works with sudo
cat /dev/null > app.log  # the classic spelling, same effect

All three leave an empty file with the same owner and permissions; each was checked down to 0 bytes. For a log that a running service keeps open this is the right move: deleting it with rm does not free the space until the service closes the file, as explained under deleted files that still use space.

cat on a server

  • Quick facts about the machine: cat /etc/os-release for the distribution, cat /proc/cpuinfo and cat /proc/meminfo for CPU and memory. The Linux version guide lists the other ways.
  • Before editing a config: cat -A on it shows whether a previous editor left tabs, CRLF line endings or trailing spaces behind.
  • Keys and certificates: cat ~/.ssh/id_ed25519.pub prints a public key to paste into another server. Never cat a private key on a shared screen or into a log.

On a KVM VPS with Debian or Ubuntu you get GNU coreutils cat, the same implementation as in the examples here, and full root, so the sudo cases apply as written. More everyday commands sit in the Linux commands cheat sheet.

Frequently asked

What does the cat command do in Linux?

cat reads one or more files and writes them to standard output in order. With one file it simply prints it; with several it joins them, which is where the name, short for concatenate, comes from. Redirect the output with > to save the joined result as a new file.

How do I append one file to another with cat?

cat extra.txt >> main.txt adds the contents of extra.txt to the end of main.txt, while a single > would replace main.txt. Never redirect into a file that is also an input, such as cat a.txt main.txt > main.txt: the shell empties main.txt before cat reads it.

How do I see tabs and Windows line endings in a file?

Run cat -A file. It marks each line end with $, shows a tab as ^I and a Windows carriage return as ^M, so lines ending in ^M$ come from a file with CRLF line endings. On macOS and BSD, cat -et shows the same.

What is the opposite of cat?

tac prints a file with the last line first. It is part of GNU coreutils on Linux. macOS does not include it; tail -r does the same job there.

Related articles

All guides