Updated: 8 October 2026 · Applies to: Debian 13, Ubuntu 24.04 and 26.04, Rocky Linux 9 (GNU coreutils 8.32 to 9.7, uutils coreutils 0.10.0 on Ubuntu 26.04, GNU grep 3.6 to 3.12, GNU diffutils 3.10 and 3.12)

Linux has a small set of commands for searching, sorting, counting and changing text in files. Each one does one job, and you chain them with a pipe (|):

grep 'text' file          # lines that contain text
sort file                 # sort lines
sort file | uniq -c       # count repeated lines
wc -l file                # count lines
cut -d' ' -f1 file        # first field of each line
tr 'a-z' 'A-Z' < file     # replace or delete characters
diff -u old new           # show what changed between two files

None of these commands changes the file; they print the result. Most examples below use a 10-line web server access log whose lines look like this:

203.0.113.7 - - [07/Oct/2026:21:00:01 +0000] "GET / HTTP/1.1" 200 5120
198.51.100.23 - - [07/Oct/2026:21:00:04 +0000] "GET /wp-login.php HTTP/1.1" 404 162

grep: find lines that match

$ grep ' 404 ' access.log
198.51.100.23 - - [07/Oct/2026:21:00:04 +0000] "GET /wp-login.php HTTP/1.1" 404 162
198.51.100.23 - - [07/Oct/2026:21:01:15 +0000] "GET /.env HTTP/1.1" 404 162
198.51.100.23 - - [07/Oct/2026:21:01:16 +0000] "GET /xmlrpc.php HTTP/1.1" 404 162
$ grep -c ' 404 ' access.log
3
  • -i ignores case, -v shows the lines that do not match, -n adds line numbers, -c counts matching lines.
  • -w matches whole words only: grep web1 servers.txt also found web10, grep -w web1 did not.
  • -E allows alternatives: grep -E '" (403|500) ' access.log finds both status codes.
  • -A 2, -B 2 and -C 2 show 2 lines after, before or around each match.
  • -F searches for the text literally. Without it the pattern is a regular expression, where . means any character: grep '.env' also matched /xenv, grep -F '.env' did not.

Exit status 0 means a match, 1 no match, 2 an error such as a missing file. To search a whole folder, see find files containing text with grep -r; for egrep and fgrep, see grep, egrep and fgrep.

sort: put lines in order

sort compares text character by character, so numbers need -n. With -k2 it sorts on the second field (fields are separated by blanks, or by the character after -t):

$ sort -k2 servers.txt
web1 16
cache1 2
db1 32
web10 4
web2 8
$ sort -k2 -n servers.txt
cache1 2
web10 4
web2 8
web1 16
db1 32
  • -r reverses, -u drops duplicate lines, -t';' sets the field separator for CSV files.
  • -h understands sizes such as 48K, 512M and 1.2G, so du -sh * | sort -h lists folders from small to large.
  • -V sorts names with numbers naturally: web1, web2, web10.

uniq: remove or count repeated lines

uniq only compares each line with the one before it, so always sort first. On the unsorted file uniq -c counted every line as 1:

$ sort fruit.txt | uniq -c
      2 apple
      3 banana
      1 cherry
$ sort fruit.txt | uniq -c | sort -rn
      3 banana
      2 apple
      1 cherry

uniq -d prints only the lines that repeat, uniq -u only the lines that appear once.

wc: count lines, words and bytes

$ wc access.log
 10 100 783 access.log
$ wc -l access.log fruit.txt
 10 access.log
  6 fruit.txt
 16 total
$ wc -l < access.log
10

The three numbers are lines, words and bytes. -l, -w and -c print one of them, -m counts characters (different from bytes in UTF-8 text). Reading the file with < prints the number without the file name, which is handy in scripts.

cut: take columns out of each line

$ cut -d' ' -f1 access.log | head -3
203.0.113.7
198.51.100.23
203.0.113.7
$ cut -d';' -f1,3 customers.csv
id;plan
101;vps-lite
102;vps-pro

-d sets the separator (one character) and -f the fields; -c1-11 cuts by character position instead. cut -d: -f1,7 /etc/passwd lists each user with their login shell. cut treats every separator as a new field, so two spaces in a row give an empty field: printf 'a b\n' | cut -d' ' -f2 printed an empty line. Squeeze the spaces first with tr -s ' ', or use awk, which splits on any run of blanks.

tr: replace, squeeze or delete characters

$ echo 'Hello World' | tr 'a-z' 'A-Z'
HELLO WORLD
$ echo 'phone: +91 98470 12345' | tr -cd '0-9\n'
919847012345

A common server use is removing Windows line endings (\r) from a file uploaded from a PC; cat -A shows them as ^M:

$ cat -A windows.conf
server_name example.com;^M$
root /var/www/html;^M$
$ tr -d '\r' < windows.conf > unix.conf && cat -A unix.conf
server_name example.com;$
root /var/www/html;$

tr reads only standard input: tr 'a-z' 'A-Z' fruit.txt fails with tr: extra operand 'fruit.txt'. More examples in tr command examples.

diff: compare two files

$ diff -u nginx.old nginx.new
--- nginx.old   2026-10-08 05:09:28.804454557 +0000
+++ nginx.new   2026-10-08 05:09:28.804454557 +0000
@@ -1,4 +1,5 @@
-worker_processes 2;
+worker_processes auto;
 keepalive_timeout 65;
-client_max_body_size 8m;
+client_max_body_size 64m;
 gzip on;
+server_tokens off;

Lines starting with - are only in the first file, + only in the second; the rest is unchanged context. diff -q just says Files nginx.old and nginx.new differ. The exit status is 0 for identical files, 1 for different, 2 for trouble. Before editing a config, keep a copy (cp site.conf site.conf.bak) so you can diff it afterwards. For folders and other tools, see compare files and directories with diff, cmp and vimdiff. If diff is not installed (it was missing in the minimal Rocky Linux 9 and AlmaLinux 10 images we tested), install it with dnf install diffutils.

Put them together

Which IP addresses made the most requests, and which status codes did the server return?

$ awk '{print $1}' access.log | sort | uniq -c | sort -rn
      4 203.0.113.7
      4 198.51.100.23
      2 192.0.2.55
$ awk '{print $9}' access.log | sort | uniq -c | sort -rn
      5 200
      3 404
      1 500
      1 403
$ awk '$9 == 404 {print $7}' access.log
/wp-login.php
/.env
/xmlrpc.php

awk numbers the blank-separated fields of each line ($1 is the IP, $7 the path, $9 the status). To replace text in a file, use sed, for example sed 's/old/new/g' file (see sed options and usage).

Common mistakes

  • sort file > file empties the file. The shell truncates it before sort reads it; in our test wc -l then reported 0 lines. Use sort -o file file.
  • Running uniq without sort, or sorting numbers without -n, as shown above.
  • Different sort order on another server. The order depends on the locale. With en_US.UTF-8, sort printed apple Apple _backup banana Cherry; with LC_ALL=C it printed Apple Cherry _backup apple banana (byte order). Put LC_ALL=C in front of sort in scripts that must give the same result everywhere.
  • Special characters in grep patterns. Quote the pattern, and use -F for literal text such as IP addresses or file names.

Ubuntu 26.04 note

On Ubuntu 26.04, sort, uniq, wc, cut, tr and paste come from uutils coreutils 0.10.0 (the Rust rewrite). Every example in this article gave the same output as GNU coreutils 9.7 on Debian 13, with one exception: sort -f (ignore case) under en_US.UTF-8 printed Apple apple banana Cherry _backup, where GNU printed apple Apple _backup banana Cherry. grep, sed, awk and diff are separate packages and are not affected.

Related: list files with ls, display file contents with cat, less, head and tail and wildcards and globbing.

Ucartz services for this topic

Was this answer helpful? 0 Users Found This Useful (0 Votes)