Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Basic regular expressions on Linux

By updated

Improve this page

Regular expressions (regex) are the common language of grep, sed and awk. Without them, you search text line by line. With them, you filter thousands of lines in one command, extract structured fields and detect anomalies in production logs.

This guide covers the POSIX regex, used natively by Linux tools, and their ERE extension, which grep -E and sed -E turn on.

What you will learn

  • Use the ^ and $ anchors, the . dot and [...] classes
  • Apply the BRE (\{n,m\}) and ERE ({n,m}, +, ?) quantifiers
  • Extract only the matched part with grep -oE
  • Clean a configuration file with sed and regex
  • Reformat data with \(...\) capture groups and \1
  • Recognise and avoid the two most frequent traps

In which context?

You do not master regex to look good on a CV: you need them as soon as you work seriously with grep, sed or awk. Concrete examples: extract every IP address from a log to block them in a firewall; detect the lines of a configuration file that do not follow an expected format before deploying it; fix variable names in bulk across dozens of configuration files; filter only the critical errors in a live log stream. Regex are cross-cutting: once you master them in grep, you reuse them immediately in sed, awk, Python, your IDE and your CI pipelines. It is one of the best-paying technical investments in system administration.

BRE vs ERE: the two POSIX dialects

Linux uses two variants of POSIX regex:

DialectTurned on byQuantifier syntax
BRE (Basic)grep, sed by default\{n,m\}; \+ and \? are GNU extensions
ERE (Extended)grep -E, sed -E, awk{n,m}, +, ?, |

The building blocks

Anchors, fix the position

Terminal window
grep '^192' servers.txt # lines starting with 192
grep '\.com$' servers.txt # lines ending with .com
grep '^$' file.txt # empty lines
grep -v '^#' config.ini # exclude comments

Anchors consume no characters: they only fix where the match must happen.

Character classes

A [...] class matches a single character among those listed:

Terminal window
grep '[0-9]' file.txt # contains a digit
grep '[a-zA-Z]' file.txt # contains a letter
grep '[^0-9]' file.txt # contains a non-digit (^ = negation)

POSIX classes are portable across distributions:

ClassEquivalentUse
[[:digit:]][0-9]Digits
[[:alpha:]][a-zA-Z] and accented lettersLetters
[[:alnum:]][a-zA-Z0-9]Letters and digits
[[:space:]][ \t\n]Spaces and tabs
[[:lower:]][a-z]Lower case
[[:upper:]][A-Z]Upper case

In a UTF-8 locale, GNU grep recognises accented letters in [[:alpha:]]: echo é | grep -c '[[:alpha:]]' answers 1, where [a-zA-Z] finds nothing. It is the opposite of tr, which works byte by byte.

Terminal window
# Lines starting with spaces (indentation)
grep '^[[:space:]]\+' config.ini
# Empty lines or lines with only spaces
grep '^[[:space:]]*$' config.ini

The dot .

. matches any character (except a newline):

Terminal window
grep 'ser.eur' servers.txt # serveur, ser_eur, serXeur (a single character)
grep '10\.0' servers.txt # literal 10.0 (escaped dot)

Quantifiers

They apply to the character or group that precedes them:

BREEREMeaning
**0 or more
\++1 or more
\??0 or 1 (optional)
\{n\}{n}Exactly n times
\{n,m\}{n,m}Between n and m times
\{n,\}{n,}n or more
Terminal window
# BRE: bytes in an IP address (1 to 3 digits)
grep '[0-9]\{1,3\}\.[0-9]\{1,3\}' servers.txt
# ERE: same thing, more readable syntax
grep -E '[0-9]{1,3}\.[0-9]{1,3}' servers.txt

grep -E: the ERE super-powers

ERE adds alternation | and parentheses () without a backslash:

Terminal window
# 4xx or 5xx error codes in an access.log
grep -E '" [45][0-9]{2} ' access.log
# Lines containing error OR warning (case-insensitive)
grep -iE 'error|warning' syslog
# Sequences of 2 to 4 adjacent digits
grep -E '[0-9]{2,4}' file.txt

grep -o: extract only the match

By default, grep prints the whole line. -o prints only the matched part:

Terminal window
# Extract every IP address from a log
grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' access.log
192.168.1.10
10.0.0.5
192.168.1.20
172.16.0.1
192.168.1.10

Combine with sort | uniq -c | sort -rn for a top of the IPs:

Terminal window
grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' access.log \
| sort | uniq -c | sort -rn | head -10

Validate a format

Terminal window
# E-mail addresses (simplified format)
grep -E '^[[:alnum:]._+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}$' file.txt
# Strict IPv4 addresses (0-255 per byte: a useful approximation)
grep -E '^([0-9]{1,3}\.){3}[0-9]{1,3}$' servers.txt

sed -E: transform with regex

Clean a configuration file

Terminal window
# Delete comments and empty lines
sed '/^#/d; /^$/d' config.ini
# Delete leading spaces
sed 's/^[[:space:]]*//' config.ini
# Normalise the separator: accept ' = ' and '=' → make it '='
sed -E 's/ *= */=/' config.ini

Chain the three:

Terminal window
sed '/^#/d; /^$/d' config.ini \
| sed 's/^[[:space:]]*//' \
| sed -E 's/ *= */=/'
serveur=web-prod-01
port=8080
timeout=30
debug=false
max_connections=100
log_level=INFO

Capture groups and backreferences

Parentheses capture part of the match, reusable with \1, \2...:

Terminal window
# Convert an ISO date to DD/MM/YYYY format
echo "2026-04-09" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/'
# → 09/04/2026
# Swap first and last name (separated by a comma)
echo "Robert, Stephane" | sed -E 's/([^,]+), ([^,]+)/\2 \1/'
# → Stephane Robert

awk with regex

awk uses ERE natively. Patterns are written between /:

Terminal window
# Print the lines whose IP is in 192.168.1.x
awk '/^192\.168\.1\./' access.log
# Lines with HTTP status 200 (field 9)
awk '$9 == 200' access.log
# Combine a field condition and a regex
awk '$9 ~ /^[45]/' access.log # status 4xx or 5xx
awk '$9 !~ /^[45]/' access.log # status other than 4xx/5xx

The ~ operator tests a regex on a field, !~ is its negation.

Summary of administration use cases

Terminal window
# Top 5 most active IPs in an access.log
grep -oE '^[0-9.]+' access.log | sort | uniq -c | sort -rn | head -5
# Every error line (ERROR or WARN, whatever the case)
grep -iE 'error|warn' /var/log/syslog | tail -20
# Extract the open ports from ss -tulnp
ss -tulnp | grep -oE ':[0-9]+' | sort -t: -k2 -n | uniq
# Check that every line of an inventory file has the right IP format
grep -vE '^([0-9]{1,3}\.){3}[0-9]{1,3}$' inventory.txt \
&& echo "Invalid format detected!"
# Clean an /etc/hosts: remove comments and empty lines
grep -vE '^[[:space:]]*(#|$)' /etc/hosts

Key points

  • grep = BRE by default; add -E for ERE as soon as you use +, ?, {n} or |.
  • grep -o extracts only the matched part: combine it with sort | uniq -c for statistics.
  • sed -E 's/pattern/replacement/' transforms; the (...) groups are recalled with \1.
  • Two traps to avoid: the unescaped dot (\. for a literal dot) and greediness ([^x]* instead of .*).
  • The POSIX classes [[:digit:]], [[:alpha:]], [[:space:]] are portable across every distribution.

Knowledge check

Check that the essentials of this guide stuck. The questions cover only what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn