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
sedand 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:
| Dialect | Turned on by | Quantifier 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
grep '^192' servers.txt # lines starting with 192grep '\.com$' servers.txt # lines ending with .comgrep '^$' file.txt # empty linesgrep -v '^#' config.ini # exclude commentsAnchors consume no characters: they only fix where the match must happen.
Character classes
A [...] class matches a single character among those listed:
grep '[0-9]' file.txt # contains a digitgrep '[a-zA-Z]' file.txt # contains a lettergrep '[^0-9]' file.txt # contains a non-digit (^ = negation)POSIX classes are portable across distributions:
| Class | Equivalent | Use |
|---|---|---|
[[:digit:]] | [0-9] | Digits |
[[:alpha:]] | [a-zA-Z] and accented letters | Letters |
[[: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.
# Lines starting with spaces (indentation)grep '^[[:space:]]\+' config.ini
# Empty lines or lines with only spacesgrep '^[[:space:]]*$' config.iniThe dot .
. matches any character (except a newline):
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:
| BRE | ERE | Meaning |
|---|---|---|
* | * | 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 |
# 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 syntaxgrep -E '[0-9]{1,3}\.[0-9]{1,3}' servers.txtgrep -E: the ERE super-powers
ERE adds alternation | and parentheses () without a backslash:
# 4xx or 5xx error codes in an access.loggrep -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 digitsgrep -E '[0-9]{2,4}' file.txtgrep -o: extract only the match
By default, grep prints the whole line. -o prints only the matched part:
# Extract every IP address from a loggrep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' access.log192.168.1.1010.0.0.5192.168.1.20172.16.0.1192.168.1.10Combine with sort | uniq -c | sort -rn for a top of the IPs:
grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' access.log \ | sort | uniq -c | sort -rn | head -10Validate a format
# 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.txtsed -E: transform with regex
Clean a configuration file
# Delete comments and empty linessed '/^#/d; /^$/d' config.ini
# Delete leading spacessed 's/^[[:space:]]*//' config.ini
# Normalise the separator: accept ' = ' and '=' → make it '='sed -E 's/ *= */=/' config.iniChain the three:
sed '/^#/d; /^$/d' config.ini \ | sed 's/^[[:space:]]*//' \ | sed -E 's/ *= */=/'serveur=web-prod-01port=8080timeout=30debug=falsemax_connections=100log_level=INFOCapture groups and backreferences
Parentheses capture part of the match, reusable with \1, \2...:
# Convert an ISO date to DD/MM/YYYY formatecho "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 Robertawk with regex
awk uses ERE natively. Patterns are written between /:
# Print the lines whose IP is in 192.168.1.xawk '/^192\.168\.1\./' access.log
# Lines with HTTP status 200 (field 9)awk '$9 == 200' access.log
# Combine a field condition and a regexawk '$9 ~ /^[45]/' access.log # status 4xx or 5xxawk '$9 !~ /^[45]/' access.log # status other than 4xx/5xxThe ~ operator tests a regex on a field, !~ is its negation.
Summary of administration use cases
# Top 5 most active IPs in an access.loggrep -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 -tulnpss -tulnp | grep -oE ':[0-9]+' | sort -t: -k2 -n | uniq
# Check that every line of an inventory file has the right IP formatgrep -vE '^([0-9]{1,3}\.){3}[0-9]{1,3}$' inventory.txt \ && echo "Invalid format detected!"
# Clean an /etc/hosts: remove comments and empty linesgrep -vE '^[[:space:]]*(#|$)' /etc/hostsKey points
grep= BRE by default; add-Efor ERE as soon as you use+,?,{n}or|.grep -oextracts only the matched part: combine it withsort | uniq -cfor 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
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
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Follow a log in real time on Linux
A new full quiz with random questions
Work again only on the questions you missed