Skip to content

3. sed, awk and grep mastery โ€‹

๐ŸŽฏ What you'll learn in this chapter:

  • sed โ€” stream editor: substitution, address ranges, in-place edit, backreferences
  • awk โ€” field-based processor: $1/$NF, BEGIN/END, arrays, counter pattern
  • Real examples: Apache log statistics, CSV transform, configuration parsing
  • macOS BSD sed vs GNU sed differences (-i gotcha)

โฑ Time: ~40 minutes ๐Ÿงช Exercises: bashlings watch โ€” 6 interactive exercises ready (exercises/08_text_advanced/)


3.1. The three pillars โ€” who does what? โ€‹

In the Unix world there are three classic tools for working with text. Each has its own role:

ToolJobExample request
grepFind โ€” filter lines"how many ERRORs are in the log?"
sedEdit โ€” substitute/delete in a stream"replace 'localhost' with 'prod' in the config file"
awkCompute โ€” fields and arithmetic"what's the sum of the CSV's 3rd column?"

Remember

grep โ€” find. sed โ€” edit. awk โ€” compute.

In most real tasks we use all three together via a pipe.

grep was covered in depth in Part 1/04. In this chapter we focus on sed and awk โ€” because they make up 60-70% of a DevOps and SRE workday.


3.2. sed basics โ€” substitution โ€‹

sed โ€” Stream EDitor. It reads from standard input (or a file), applies a specified command to each line, and prints the result to stdout.

The most basic operation โ€” s/PATTERN/REPLACEMENT/ โ€‹

bash
echo "salom dunyo" | sed 's/dunyo/bash/'
# salom bash

Format: s/<searched>/<replacement>/[flags]

g flag โ€” replace all matches โ€‹

By default sed replaces only the first match on each line:

bash
echo "ola bola ola" | sed 's/ola/X/'
# X bola ola      โ† only the first

echo "ola bola ola" | sed 's/ola/X/g'
# X bola X        โ† all

Working with a file โ€‹

bash
# File as stdin
sed 's/old/new/g' file.txt

# Or as an argument (same thing)
sed 's/old/new/g' < file.txt

The result goes to stdout โ€” the file itself is not changed (this is not in-place edit).

Other useful flags โ€‹

FlagMeaning
gglobal โ€” all matches on each line
icase-insensitive (GNU sed)
2replace only the 2nd match
padditionally print the replaced line (with -n)
bash
# Make the 2nd "ola" into X, leave the rest
echo "ola bola ola" | sed 's/ola/X/2'
# ola bola X

Changing the delimiter โ€‹

If the pattern contains /, instead of escaping it, use a different character:

bash
# Classic way โ€” escaping
echo "/usr/local/bin" | sed 's/\/usr\//\/opt\//'
# /opt/local/bin    โ† hard to read

# Better โ€” make `|` or `,` the delimiter
echo "/usr/local/bin" | sed 's|/usr/|/opt/|'
# /opt/local/bin    โ† clean

3.3. Address โ€” which line to apply to โ€‹

By default sed applies the command to every line. With an address you can restrict it.

By line number โ€‹

bash
# Replace only on line 3
sed '3 s/foo/bar/' file.txt

# Range: from 5 to 10
sed '5,10 s/foo/bar/' file.txt

# Last line โ€” $
sed '$ s/foo/bar/' file.txt

By regex โ€‹

bash
# Only on lines containing the word "ERROR"
sed '/ERROR/ s/timeout/TIMEOUT/' log.txt

# Lines between "begin" and "end"
sed '/begin/,/end/ s/foo/bar/' file.txt

Negation โ€” ! โ€‹

bash
# Everywhere except line 5
sed '5! s/foo/bar/' file.txt

# On non-empty lines
sed '/^$/! s/foo/bar/' file.txt

3.4. Other sed commands โ€‹

Substitution is just the beginning. sed is versatile โ€” below are the most essential ones.

d โ€” delete a line โ€‹

bash
# Delete line 1 (header skip)
sed '1d' data.csv

# Delete lines 5-10
sed '5,10d' file.txt

# Delete blank lines
sed '/^$/d' file.txt

# Delete lines containing "DEBUG"
sed '/DEBUG/d' log.txt

p + -n โ€” print only the specified lines โ€‹

p โ€” print the line. -n โ€” disable the default output.

bash
# Only lines 5-10 (in Part 1/04 this was done via head|tail)
sed -n '5,10p' file.txt

# Only lines containing "ERROR" (grep equivalent)
sed -n '/ERROR/p' log.txt

# First 3 lines
sed -n '1,3p' file.txt

i and a โ€” insert/append โ€‹

bash
# Add text before line 3
sed '3i\
yangi qator
' file.txt

# Add text after line 3
sed '3a\
qoshilgan qator
' file.txt

macOS BSD vs GNU sed

GNU sed (Linux) supports i\ and a\ inline:

bash
sed '3i\new line' file   # GNU OK, BSD ERROR

On macOS you always need a newline (like the example above).

Multiple commands โ€” -e or ; โ€‹

bash
# Both substitute and delete
sed -e 's/foo/bar/g' -e '/DEBUG/d' file.txt

# Or with a semicolon
sed 's/foo/bar/g; /DEBUG/d' file.txt

3.5. In-place edit โ€” -i flag โ€‹

So far the sed result went to stdout. With -i you can modify the file in place.

macOS problem

This is the biggest difference between GNU sed and BSD sed.

bash
# GNU sed (Linux)
sed -i 's/foo/bar/g' file.txt          # โœ“ works

# BSD sed (macOS)
sed -i 's/foo/bar/g' file.txt          # โŒ error
sed -i '' 's/foo/bar/g' file.txt       # โœ“ works (empty string)
sed -i.bak 's/foo/bar/g' file.txt      # โœ“ on both (creates a backup)

Cross-platform safe way: always use -i.bak. An extra file.txt.bak is created, but it works on both systems.

bash
# Cross-platform pattern
sed -i.bak 's/old/new/g' config.txt
rm -f config.txt.bak

3.6. Backreferences โ€” returning a group โ€‹

In the pattern, \(...\) (or (...) with -E) is a group. In the replacement it is referenced via \1, \2.

bash
# Format a phone number
echo "tel 998901234567" | sed -E 's/([0-9]{3})([0-9]{2})([0-9]{7})/+\1 \2 \3/'
# tel +998 90 1234567
bash
# Extract the extension from a filename
echo "report.tar.gz" | sed -E 's/(.+)\.(tar\.gz)$/Nom: \1, Tip: \2/'
# Nom: report, Tip: tar.gz

-E (extended regex)

sed -E โ€” Perl-like regex. (, ), +, ?, | can be used directly (no escaping needed).

In GNU, -r is the same. On macOS, using -E is portable across both GNU and BSD.


3.7. awk basics โ€” field-based processor โ€‹

awk is a mini-language. It automatically splits the input into fields (by whitespace by default) and lets you reference them via $1, $2, ...

The simplest example โ€‹

bash
echo "Ali 25 Toshkent" | awk '{ print $1 }'
# Ali

echo "Ali 25 Toshkent" | awk '{ print $2 }'
# 25

echo "Ali 25 Toshkent" | awk '{ print $1, $3 }'
# Ali Toshkent

$0 โ€” the whole line.

With a file โ€‹

bash
# The first column of /etc/passwd (usernames)
awk -F':' '{ print $1 }' /etc/passwd

-F':' โ€” input field separator (default whitespace).

Built-in variables โ€‹

VariableMeaning
$0The whole line
$1, $2, ...The Nth field
$NFThe last field (NF = Number of Fields)
NFNumber of fields on the current line
NRCurrent line number
FSInput field separator
OFSOutput field separator (default whitespace)
FILENAMECurrent file name
bash
# With line numbers
awk '{ print NR, $0 }' file.txt

# Only the last column
awk '{ print $NF }' file.txt

# Field count and first field
awk '{ print NF, $1 }' file.txt

3.8. Pattern + action syntax โ€‹

awk syntax: pattern { action }. Both are optional.

bash
# 1. Action only โ€” applied to each line
awk '{ print $1 }' file

# 2. Pattern only โ€” prints matching lines
awk '/ERROR/' log.txt           # grep equivalent

# 3. Pattern + action
awk '/ERROR/ { print $1 }' log.txt

Conditional pattern โ€‹

bash
# Lines where the first field is "ERROR"
awk '$1 == "ERROR" { print }' log.txt

# Second column > 100
awk '$2 > 100 { print $1, $2 }' scores.txt

# Or: the whole line contains "FAIL"
awk '/FAIL/ { print NR, $0 }' log.txt

Logical operators โ€‹

bash
# Both ERROR and 500
awk '/ERROR/ && /500/' log.txt

# Or either of the two
awk '/ERROR/ || /FATAL/' log.txt

# Negation
awk '!/DEBUG/' log.txt

3.9. BEGIN and END blocks โ€‹

BEGIN { } โ€” runs once before any line is read. END { } โ€” runs once after all lines have been read.

Classic counter โ€‹

bash
awk '
BEGIN { count = 0 }
/ERROR/ { count++ }
END   { print "Jami xatolar:", count }
' app.log

Table with a header โ€‹

bash
awk '
BEGIN { print "ID\tFOYDALANUVCHI" }
{ print NR "\t" $1 }
' users.txt

Sum โ€‹

bash
# Sum of the first column
awk '{ sum += $1 } END { print sum }' numbers.txt

# Average value
awk '{ sum += $1; n++ } END { print sum/n }' numbers.txt

3.10. Arrays โ€” counter pattern โ€‹

In awk, arrays are string-indexed (just like an associative array).

Word frequency โ€‹

bash
echo "salom dunyo bash salom uz bash bash" | \
  awk '{
    for (i=1; i<=NF; i++) count[$i]++
  } END {
    for (word in count) print count[word], word
  }'

Result:

text
1 dunyo
1 uz
2 salom
3 bash

IP statistics in an Apache access log โ€‹

bash
awk '{ count[$1]++ } END {
  for (ip in count) print count[ip], ip
}' access.log | sort -rn | head -10

The top 10 IPs that sent the most requests.

Sum by category โ€‹

bash
# users.txt:
#   ali     25  IT
#   vali    30  IT
#   gulnora 28  HR
#   bobur   35  IT

awk '{ sum[$3] += $2 } END {
  for (dept in sum) print dept, sum[dept]
}' users.txt

Result:

text
IT 90
HR 28

3.11. printf โ€” formatted output โ€‹

print is simple. printf โ€” formatted like in C:

bash
awk '{ printf "%-10s %5d\n", $1, $2 }' data.txt

%-10s โ€” left-aligned 10-character text. %5d โ€” a 5-character number (right-aligned). \n โ€” newline (automatic in print, must be added in printf).

Example:

bash
echo "ali 100
bobur 99
saida 1000" | awk '{ printf "%-10s %5d\n", $1, $2 }'
# ali          100
# bobur         99
# saida       1000

3.12. Control logic (if, for, while) โ€‹

Inside an awk action block you can write full programs:

bash
# if/else
awk '{
  if ($2 >= 60) print $1, "passed"
  else          print $1, "failed"
}' scores.txt

# for loop
awk '{
  for (i=1; i<=NF; i++) print i ":", $i
}' file.txt

# while
awk '{
  i = 1
  while (i <= NF) { print $i; i++ }
}' file.txt

3.13. Real production examples โ€‹

Example 1 โ€” Apache log statistics โ€‹

access.log in standard format:

192.168.1.5 - - [10/Oct/2026:14:22:01] "GET /api/users HTTP/1.1" 200 1234
192.168.1.5 - - [10/Oct/2026:14:22:02] "GET /api/posts HTTP/1.1" 404 89
...

Top 10 IPs:

bash
awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -10

Statistics by status code:

bash
awk '{ print $9 }' access.log | sort | uniq -c | sort -rn

Only 5xx errors:

bash
awk '$9 >= 500 { print }' access.log

Largest response (last column โ€” bytes):

bash
awk '{ print $NF, $7 }' access.log | sort -rn | head -5

Example 2 โ€” Disk usage analysis โ€‹

Sort the result of du -sh ~/* by size (sort -h also exists, but here with awk):

bash
du -sh ~/* 2>/dev/null | sort -hr | head -10

Or filter with awk โ€” those larger than 100MB:

bash
du -k ~/* 2>/dev/null | awk '$1 > 102400 { print $1/1024 "MB", $2 }' | sort -rn

Example 3 โ€” CSV transform โ€‹

users.csv:

ali,25,toshkent
vali,30,samarqand
gulnora,28,buxoro

Only those aged 27+:

bash
awk -F',' '$2 >= 27 { print $1, $3 }' users.csv
# vali samarqand
# gulnora buxoro

Output as JSON:

bash
awk -F',' '
BEGIN { print "[" }
{ printf "  {\"name\":\"%s\",\"age\":%d,\"city\":\"%s\"}%s\n",
    $1, $2, $3, (NR==total ? "" : ",")
}
END { print "]" }
' total=$(wc -l < users.csv) users.csv

Example 4 โ€” Updating configuration โ€‹

bash
# Change the port in nginx.conf
sed -i.bak 's/listen 80;/listen 8080;/' /etc/nginx/nginx.conf

# Change API_KEY in the .env file
sed -i.bak 's/^API_KEY=.*/API_KEY=new-secret-key/' .env

# Update the package.json version
sed -i.bak -E 's/"version": "[0-9.]+"/"version": "2.0.0"/' package.json

Example 5 โ€” Log file analysis โ€‹

bash
# Most frequent errors in the last 100 lines
tail -n 100 app.log \
  | awk '/ERROR/ { print $4 }' \
  | sort | uniq -c | sort -rn | head -5

3.14. sed vs awk โ€” when to use what? โ€‹

TaskChoiceReason
Replace a single patternsedShorter, faster
Delete / slice out linessedd, p, address syntax
Working with columnsawk$1, $2 field-based
Arithmetic operationsawksum += $2, count++
Conditional logicawkif/else, for, while
Counter / aggregateawkcount[$1]++ paradigm
In-place editsed -iin awk it's -i inplace (GNU only)
Multiline transformationawkor Perl, sed is complicated

Rule of thumb

  • sed โ€” simple substitution at the s/A/B/ level
  • awk โ€” any logic working with multiple fields
  • When both get complicated โ€” it's time to move to python or perl

3.15. Modern alternatives โ€‹

sed and awk were created in the 1970s. Modern alternatives:

ToolJobInstallation
ripgrep (rg)a faster version of grepbrew install ripgrep
sda simple alternative to sedcargo install sd
miller (mlr)unified tool for CSV/JSON/awkbrew install miller
jqJSON parser (powerful)brew install jq
xsvdedicated to CSVcargo install xsv

But: sed and awk are available on every system. Learn them first, then add modern tools.


3.16. Common mistakes โ€‹

Classic pitfalls

  1. macOS sed -i errors without an empty argument.

    bash
    sed -i 's/old/new/' file       # โŒ macOS
    sed -i '' 's/old/new/' file    # โœ“ macOS
    sed -i.bak 's/old/new/' file   # โœ“ both GNU and BSD
  2. / is awkward to escape โ€” change the delimiter.

    bash
    sed 's/\/usr\/bin\///'   # โŒ unreadable
    sed 's|/usr/bin/||'      # โœ“
  3. Forgetting the g flag.

    bash
    sed 's/foo/bar/'  fayl    # replaces only the 1st match
    sed 's/foo/bar/g' fayl    # all of them
  4. Using a variable with $ in awk (confusing it with Bash).

    bash
    awk '{ x = 5; print $x }'    # $x = the 5th field
    awk '{ x = 5; print x }'     # the value of x = 5
  5. Forgetting -E (extended regex) โ€” (...) doesn't work.

    bash
    sed 's/(foo|bar)/X/g'      # โŒ literal (
    sed -E 's/(foo|bar)/X/g'   # โœ“
  6. Not changing the awk separator.

    bash
    awk '{ print $1 }' file.csv          # โŒ treated as whitespace โ€” broken
    awk -F',' '{ print $1 }' file.csv    # โœ“
  7. Confusing NR and NF.

    • NR = line number
    • NF = field count

3.17. Exercises โ€‹

๐Ÿงช Bashlings โ€” interactive exercises

This chapter's 6 exercises with auto-checking via the bashlings CLI:

bash
bashlings watch              # start from the first pending exercise
bashlings run sed1           # check a single exercise
bashlings hint sed1          # step-by-step hint

Source: exercises/08_text_advanced/

And try the following conceptual exercises by hand yourself:

  1. Email mask โ€” turn ali@example.com into a***@example.com. With sed regex.

  2. Clean up blank lines โ€” write a sed chain that deletes all blank and whitespace-only lines from a file.

  3. Top 5 words โ€” from an input text file, print the 5 most frequent words and their counts (awk + sort).

  4. CSV averager โ€” write an awk script: compute the average value of the (numeric) 3rd column of a CSV file.

  5. Update the version โ€” a cross-platform script (macOS + Linux) that changes "version": "X.Y.Z" inside package.json to a new "version": "1.2.3".


3.18. Summary โ€‹

ConceptKey point
sed 's/A/B/'Replaces the first A with B
sed 's/A/B/g'All matches
sed '5,10d' faylDeletes lines 5-10
sed -n '/pattern/p'Only matching lines
sed -E 's/(...)/\1/'Backreference + extended regex
sed -i.bak '...' faylIn-place edit (cross-platform safe)
awk '{ print $1 }'The first field
awk -F','Input separator โ€” comma
awk '$2 > 10'Conditional filter
awk 'NR==1'Only line 1
BEGIN { } / END { }Start/end blocks
count[$1]++Counter pattern (associative array)

5 key ideas โ€‹

  1. grep finds, sed edits, awk computes. Choose the right one based on the task.
  2. macOS BSD sed โ€” always use -i.bak (or install gnu-sed).
  3. Counter pattern (count[$1]++; END { for ... }) โ€” the most used idiom in DevOps.
  4. $NF for the last field โ€” ideal in log files for response size, end-of-line.
  5. Combine in a pipeline โ€” grep | awk | sort | uniq -c | head is a real workflow.

๐ŸŽ‰ Now you have industrial-grade text-processing skills. In the next chapter we'll learn to write robust scripts via signals and traps โ€” Ctrl+C being pressed, cleanup, graceful shutdown.

Next page: 4. Signals and traps โ†’

Released under the MIT License.