Stream editing, substitution, addressing, regex, and safe file cleanup
sed stands for stream editor. It is a non-interactive text processing tool used to transform input from a file or pipeline.
| Reason | Explanation |
|---|---|
| Speed | Processes massive files line-by-line without loading entire file into memory |
| Automation | Perfect for scripts - no manual editing needed |
| Precision | Regex-powered pattern matching for surgical edits |
| Pipelines | Works seamlessly with other tools (grep, awk, cat, etc.) |
| Portability | Available on virtually every Unix/Linux system |
awk)ed line editor (hence "stream ed-itor")sed was one of the earliest tools to support regular expressions (regex), making it a pioneer in text processing.
| Role | Use Case |
|---|---|
| SysAdmins | Config file modifications, log parsing |
| DevOps Engineers | CI/CD pipeline text transformations |
| Penetration Testers | Parsing tool outputs, cleaning wordlists |
| Developers | Batch code refactoring, data cleaning |
| Data Analysts | CSV/text preprocessing |
sed --version
# or
which sed
# Usually pre-installed. If not:
sudo apt update && sudo apt install sed
sudo dnf install sed
# or
sudo yum install sed
# macOS has BSD sed (slightly different syntax)
# For GNU sed (recommended for compatibility):
brew install gnu-sed
# Use as 'gsed' or add to PATH:
export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
Option 1: WSL (Recommended)
# Enable WSL, install Ubuntu, sed comes pre-installed
wsl --install
Option 2: Git Bash - Install Git for Windows (includes sed)
Option 3: GnuWin32 - Download from gnuwin32.sourceforge.net
Option 4: Cygwin - Install Cygwin with sed package
sed is installed by default. Open a terminal and verify it before starting the exercises.
sed [OPTIONS] 'COMMAND' [INPUT_FILE]
Or with pipeline:
cat file.txt | sed 'COMMAND'
| Component | Description |
|---|---|
sed |
The command itself |
[OPTIONS] |
Flags that modify behavior (-i, -n, -e, etc.) |
'COMMAND' |
The operation to perform (in single quotes) |
[INPUT_FILE] |
File to process (optional if using pipeline) |
sed '[ADDRESS]OPERATION[/PATTERN/REPLACEMENT/[FLAGS]]'
| Part | Meaning |
|---|---|
ADDRESS |
Which lines to operate on (line number, range, or pattern) |
OPERATION |
What to do (s=substitute, d=delete, p=print, etc. ) |
PATTERN |
What to find (supports regex) |
REPLACEMENT |
What to replace with |
FLAGS |
Modifiers (g=global, i=case-insensitive, etc.) |
| Flag | Long Form | Description |
|---|---|---|
-n |
--quiet / --silent |
Suppress automatic printing; only print when explicitly requested with p |
-e |
--expression=SCRIPT |
Add multiple commands in one sed call |
-f |
--file=SCRIPT_FILE |
Read commands from a file |
-i |
--in-place[=SUFFIX] |
Edit file in-place (modify original file) |
-i.bak |
- | Edit in-place but create backup with .bak extension |
-r / -E |
--regexp-extended |
Use Extended Regular Expressions (ERE) |
-s |
--separate |
Treat each file as separate stream |
-u |
--unbuffered |
Load minimal data and flush output buffers frequently |
-z |
--null-data |
Separate lines by NUL character (not newline) |
--help |
- | Display help message |
--version |
- | Display version info |
-i modifies the original file! Always test without -i first or use -i.bak for backup.
s command)| Flag | Description |
|---|---|
g |
Global - Replace all occurrences in line (not just first) |
p |
Print - Print the modified line |
i / I |
Case-insensitive matching |
NUMBER |
Replace only the Nth occurrence (e.g., 2 = second match) |
w FILE |
Write - Write modified lines to FILE |
| Space | Description |
|---|---|
| Pattern Space | Temporary buffer where current line is held for processing |
| Hold Space | Secondary buffer for storing data across lines (advanced) |
| Address Type | Example | Description |
|---|---|---|
| Line Number | 5 |
Only line 5 |
| Line Range | 5,10 |
Lines 5 through 10 |
| Start to End | 5,$ |
Line 5 to last line ($ = last line) |
| Pattern | /error/ |
Lines containing "error" |
| Pattern Range | /start/,/end/ |
From line matching "start" to line matching "end" |
| Step | 1~2 |
Every odd line (start at 1, step by 2) |
| Negation | 5! |
All lines EXCEPT line 5 |
| Command | Name | Description |
|---|---|---|
s |
Substitute | Find and replace |
d |
Delete | Remove lines |
p |
Output lines (use with -n) |
|
a |
Append | Add text AFTER current line |
i |
Insert | Add text BEFORE current line |
c |
Change | Replace entire line with new text |
y |
Transform | Character-by-character replacement (like tr) |
q |
Quit | Stop processing after current line |
r |
Read | Insert contents of another file |
w |
Write | Write pattern space to file |
= |
Line Number | Print current line number |
n |
Next | Skip to next line |
N |
Append Next | Append next line to pattern space |
h |
Hold | Copy pattern space to hold space |
H |
Hold Append | Append pattern space to hold space |
g |
Get | Copy hold space to pattern space |
G |
Get Append | Append hold space to pattern space |
x |
Exchange | Swap pattern and hold spaces |
| Symbol | Meaning |
|---|---|
. |
Any single character |
* |
Zero or more of previous character |
^ |
Start of line |
$ |
End of line |
[ ] |
Character class |
[^ ] |
Negated character class |
\( \) |
Capture group (escaped parentheses) |
\1, \2 |
Backreference to capture group |
\{n\} |
Exactly n occurrences |
\{n,m\} |
Between n and m occurrences |
-E or -r| Symbol | Meaning |
|---|---|
+ |
One or more of previous character |
? |
Zero or one of previous character |
( ) |
Capture group (no escaping needed) |
| |
OR operator |
{n,m} |
Quantifier (no escaping needed) |
The / is the default delimiter, but you can use ANY character:
# Default
sed 's/old/new/'
# Using | as delimiter (useful when pattern contains /)
sed 's|/path/to/old|/path/to/new|'
# Using # as delimiter
sed 's#old#new#'
# Using @ as delimiter
sed 's@old@new@'
| or # to avoid escaping slashes.
| Character | Meaning |
|---|---|
& |
The entire matched pattern |
\1 - \9 |
Backreference to capture groups |
\U |
Uppercase the replacement |
\L |
Lowercase the replacement |
\u |
Uppercase first character |
\l |
Lowercase first character |
\E |
End case conversion |
Create a file called emp.csv with 35 employee records and 7 fields:
Fields: EmpID, Name, Department, Designation, Salary, JoinDate, City
cat << 'EOF' > emp.csv
EmpID,Name,Department,Designation,Salary,JoinDate,City
E001,Raj Kumar,IT,Developer,75000,2020-03-15,Mumbai
E002,Priya Sharma,HR,Manager,85000,2019-07-22,Delhi
E003,Amit Singh,IT,Senior Developer,95000,2018-01-10,Bangalore
E004,Sneha Patel,Finance,Analyst,65000,2021-05-18,Chennai
E005,Vikram Reddy,IT,Team Lead,110000,2017-09-05,Hyderabad
E006,Anjali Gupta,Marketing,Executive,55000,2022-02-14,Pune
E007,Rahul Verma,IT,Developer,72000,2020-08-30,Mumbai
E008,Kavita Nair,HR,Recruiter,48000,2021-11-25,Kochi
E009,Suresh Menon,Finance,Manager,98000,2016-04-12,Bangalore
E010,Deepa Krishnan,IT,Developer,70000,2020-06-08,Chennai
E011,Arjun Mehta,Sales,Executive,52000,2022-01-20,Delhi
E012,Pooja Iyer,IT,QA Engineer,68000,2019-10-15,Hyderabad
E013,Manoj Tiwari,Operations,Supervisor,62000,2018-12-03,Lucknow
E014,Ritu Saxena,Marketing,Manager,92000,2017-03-28,Mumbai
E015,Karthik Rajan,IT,DevOps Engineer,105000,2019-05-10,Bangalore
E016,Neha Agarwal,Finance,Analyst,63000,2021-07-14,Noida
E017,Sanjay Dubey,IT,Senior Developer,98000,2018-08-22,Pune
E018,Meera Joshi,HR,Manager,88000,2017-11-30,Mumbai
E019,Varun Kapoor,Sales,Manager,95000,2016-06-18,Delhi
E020,Lakshmi Rao,IT,Developer,74000,2020-04-25,Hyderabad
E021,Gaurav Mishra,Operations,Manager,90000,2018-02-08,Kolkata
E022,Swati Bose,Marketing,Analyst,58000,2021-09-12,Kolkata
E023,Nikhil Jain,IT,Architect,130000,2015-10-20,Bangalore
E024,Divya Pillai,Finance,Senior Analyst,82000,2019-01-15,Chennai
E025,Rohit Choudhary,IT,Team Lead,108000,2018-04-30,Gurgaon
E026,Aarti Deshmukh,HR,Executive,45000,2022-03-08,Pune
E027,Prasad Kulkarni,IT,Developer,71000,2020-12-18,Bangalore
E028,Nandini Hegde,Sales,Executive,54000,2021-06-22,Mysore
E029,Abhishek Pandey,IT,QA Lead,88000,2019-02-28,Noida
E030,Shruti Malhotra,Marketing,Senior Executive,72000,2020-01-10,Delhi
E031,Rajesh Nambiar,Finance,Manager,96000,2017-07-25,Kochi
E032,Pallavi Sinha,IT,Developer,69000,2021-03-05,Patna
E033,Venkat Subramanian,Operations,Analyst,60000,2020-09-14,Chennai
E034,Geeta Reddy,IT,Senior Developer,97000,2018-06-12,Hyderabad
E035,Anand Sharma,IT,CTO,180000,2014-01-01,Mumbai
EOF
Verify the file:
cat emp.csv | head -5 # View first 5 lines
wc -l emp.csv # Should show 36 (header + 35 records)
s command)sed 's/IT/Technology/g' emp.csv
s - substitute command/IT/ - pattern to find/Technology/ - replacement textg - global flag (replace all occurrences in each line)sed 's/Developer/Dev/' emp.csv
g flag, only the first match per line is replaced. sed 's/a/@/2' emp.csv
2 - replace only the 2nd occurrencesed 's/manager/BOSS/gi' emp.csv
i - case-insensitive matchingg - all occurrencessed 's|2020-03-15|15/03/2020|' emp.csv
| as delimiter to avoid escaping / in the replacementsed -n '5p' emp.csv
-n - suppress automatic printing5 - address (line 5)p - print commandsed -n '10,15p' emp.csv
10,15 - range from line 10 to 15sed -n '30,$p' emp.csv
$ - represents the last linesed -n '1~2p' emp.csv
1~2 - start at line 1, then every 2nd linesed -n '/Mumbai/p' emp.csv
/Mumbai/ - pattern addresssed -n '/IT/!p' emp.csv
! - negation (NOT)d)sed '1d' emp.csv
1 - line 1d - delete commandsed '$d' emp.csv
sed '5,10d' emp.csv
sed '/Bangalore/d' emp.csv
sed '/^$/d' emp.csv
^$ - regex for empty line (start immediately followed by end)sed '/HR/d; /Finance/d' emp.csv
# OR
sed -e '/HR/d' -e '/Finance/d' emp.csv
i, a, c)sed '1i # Employee Database - Confidential' emp.csv
1 - line 1i - insert beforesed '$a --- END OF RECORDS ---' emp.csv
sed '5c RECORD REDACTED' emp.csv
sed '/CTO/c [EXECUTIVE DATA HIDDEN]' emp.csv
sed -n 's/^[^,]*,\([^,]*\),. */\1/p' emp.csv
^[^,]*, - match EmpID and first comma\([^,]*\) - capture group 1: Name (everything until next comma),.* - match rest of line\1 - backreference to captured Name-n with p - only print modified linesAlternative (Extended Regex):
sed -E -n 's/^[^,]*,([^,]*),.*/\1/p' emp.csv
sed -E 's/,([A-Za-z]+) ([A-Za-z]+),/,\2 \1,/g' emp.csv
([A-Za-z]+) - capture first name - space([A-Za-z]+) - capture last name\2 \1 - swap ordersed -E 's/,[0-9]{5,6},/,XXXXX,/g' emp.csv
[0-9]{5,6} - match 5-6 digit numbers (salaries)sed -E 's/,([0-9]{5,6}),/,Rs.\1,/g' emp.csv
sed -E 's/,(IT|HR|Finance|Marketing|Sales|Operations),/,[\1],/g' emp.csv
& represents the entire matched pattern# Extract names and create email format
sed -E -n 's/^[^,]*,([A-Za-z]+) ([A-Za-z]+),.*/\L\1.\2\[email protected]/p' emp.csv
\L...\E - convert to lowercase\1.\2 - firstname.lastname format# Method 1: Semicolon separation
sed 's/IT/Tech/g; s/HR/Human Resources/g' emp.csv
# Method 2: Multiple -e flags
sed -e 's/IT/Tech/g' -e 's/HR/Human Resources/g' emp.csv
# Method 3: Using sed script file
echo -e 's/IT/Tech/g\ns/HR/Human Resources/g' > commands.sed
sed -f commands.sed emp.csv
sed '1d; s/Developer/SDE/g' emp.csv
-i)# Create backup first!
cp emp.csv emp_backup.csv
# Edit in-place with backup
sed -i.bak 's/Mumbai/Bombay/g' emp.csv
# Verify
grep "Bombay" emp.csv
-i.bak - edit in-place, create backup with .bak extension-i without backup suffix modifies the file permanently with no undo. Always test without -i first.
# Linux (GNU sed)
sed -i 's/old/new/g' file.txt
# macOS (BSD sed) - requires empty string for no backup
sed -i '' 's/old/new/g' file.txt
# macOS with backup
sed -i '.bak' 's/old/new/g' file.txt
sed 's/,/\t/g' emp.csv > emp. tsv
sed 's/[[:space:]]*$//' filename.txt
[[:space:]] - matches any whitespace (space, tab)*$ - zero or more at end of linesed 's/^[[:space:]]*//' filename. txt
sed = emp.csv | sed 'N; s/\n/\t/'
= - prints line numberN - append next line to pattern spaces/\n/\t/ - replace newline with tabAlternative (simpler tools):
nl emp.csv
# or
cat -n emp.csv
sed -n '/,IT,/p' emp.csv
sed -n '/^E010/,/^E020/p' emp.csv
/pattern1/,/pattern2/ - range between two patternssed '/Finance/s/^/#/' emp.csv
/Finance/ - address: lines containing Finances/^/#/ - substitute start of line with #sed 'G' emp.csv
G - append hold space (empty by default) to pattern spacesed '/^$/d' emp.csv
sed 's/. */\U&/' emp.csv
.* - match entire line\U - uppercase what follows& - the entire matched pattern| Task | grep | sed | awk |
|---|---|---|---|
| Search/filter lines | Yes Best | Yes Good | Yes Good |
| Simple find/replace | No | Yes Best | Yes Good |
| Complex find/replace | No | Yes Best | Yes Good |
| Field/column extraction | No | Warning Possible | Yes Best |
| Numeric comparisons | No | No | Yes Best |
| Arithmetic operations | No | No | Yes Best |
| Line insertion/deletion | No | Yes Best | Yes Good |
| In-place file editing | No | Yes Best | Warning Limited |
| Speed on large files | Yes Fastest | Yes Fast | Warning Slower |
| Scenario | Best Tool |
|---|---|
| "Find all lines with error" | grep |
| "Replace all foo with bar" | sed |
| "Print 3rd column of CSV" | awk |
| "Sum of all values in column" | awk |
| "Delete lines 5-10" | sed |
| "Find IPs in log file" | grep -E |
| "Transform date format" | sed |
| "Filter rows where col3 > 100" | awk |
# Example 1: Find and count IT employees
grep ",IT," emp.csv | wc -l
# Example 2: Extract IT employee names
grep ",IT," emp.csv | sed -E 's/^[^,]*,([^,]*),.*$/\1/'
# Example 3: Average salary of IT department
grep ",IT," emp.csv | awk -F',' '{sum+=$5; count++} END {print sum/count}'
# Example 4: Pipeline magic - IT devs in Mumbai, sorted by salary
grep ",IT," emp.csv | grep "Mumbai" | awk -F',' '{print $5, $2}' | sort -nr
Try these WITHOUT looking at solutions first! These test your complete understanding.
Create a fake log file and clean it:
echo -e "2024-01-01 INFO Starting\n2024-01-01 DEBUG Details\n2024-01-01 ERROR Failed\n2024-01-01 DEBUG More\n2024-01-01 INFO Done" > test. log
Task: Remove all DEBUG lines AND replace INFO with [INFO].
sed '/DEBUG/d; s/INFO/[INFO]/g' test. log
Create a config file:
echo -e "host=localhost\nport=8080\ndebug=true\ntimeout=30" > config.ini
Task: Change port to 3000 AND debug to false (in-place with backup).
sed -i.bak 's/port=8080/port=3000/; s/debug=true/debug=false/' config.ini
Task: In emp.csv, mask all dates to show only year (2020-03-15 -> 2020-XX-XX).
sed -E 's/([0-9]{4})-[0-9]{2}-[0-9]{2}/\1-XX-XX/g' emp.csv
Task: Add a 3-line header comment to emp.csv:
# Employee Database
# Last Updated: 2024
# Confidential
sed '1i # Employee Database\n# Last Updated: 2024\n# Confidential' emp.csv
# Or with multiple -e:
sed -e '1i # Employee Database' -e '1i # Last Updated: 2024' -e '1i # Confidential' emp.csv
Task: Extract only CTO record, swap the name, and uppercase the designation.
sed -n '/CTO/p' emp.csv | sed -E 's/,([A-Za-z]+) ([A-Za-z]+),/,\2 \1,/' | sed 's/CTO/\U&/'
Task: Add line numbers only to lines that contain "Developer".
sed -n '/Developer/=' emp.csv | sed 'N; s/\n/ - /'
# Better approach with grep + nl:
grep -n "Developer" emp.csv
Task: Print all lines that do NOT contain "Developer" AND do NOT contain "Manager".
sed -n '/Developer/! {/Manager/! p}' emp.csv
# Or with delete:
sed '/Developer/d; /Manager/d' emp.csv
sed 's/old/new/' # Replace first occurrence
sed 's/old/new/g' # Replace all occurrences
sed 's/old/new/gi' # Case-insensitive, all occurrences
sed 's/old/new/2' # Replace 2nd occurrence only
sed 's/old/new/gp' # Replace and print
sed 's|/old|/new|' # Use | delimiter for paths
sed '5s/old/new/' # Only on line 5
sed '5,10s/old/new/' # Lines 5-10
sed '5,$s/old/new/' # Line 5 to end
sed '/pattern/s/old/new/' # Lines matching pattern
sed '1~2s/old/new/' # Every odd line
sed '0~2s/old/new/' # Every even line
sed '5d' # Delete line 5
sed '5,10d' # Delete lines 5-10
sed '$d' # Delete last line
sed '/pattern/d' # Delete matching lines
sed '/^$/d' # Delete empty lines
sed '/^#/d' # Delete comment lines
sed -n '5p' # Print line 5 only
sed -n '5,10p' # Print lines 5-10
sed -n '/pattern/p' # Print matching lines
sed -n '/start/,/end/p' # Print between patterns
sed '5i New line' # Insert before line 5
sed '5a New line' # Append after line 5
sed '5c New line' # Replace line 5
sed '$a End of file' # Append at end
sed -i 's/old/new/g' file # Edit directly (Linux)
sed -i.bak 's/old/new/g' file # Edit with backup
sed -i '' 's/old/new/g' file # Edit directly (macOS)
sed 's/a/A/g; s/b/B/g' # Semicolon separated
sed -e 's/a/A/' -e 's/b/B/' # Multiple -e flags
sed -f script.sed file # Commands from file
sed 's/. */[&]/' # & = matched pattern
sed 's/\(word\)/[\1]/' # \1 = backreference
sed 's/. */\U&/' # Uppercase all
sed 's/. */\L&/' # Lowercase all
sed 's/./\u&/' # Capitalize first char
sed 's/[[:space:]]*$//' # Remove trailing whitespace
sed 's/^[[:space:]]*//' # Remove leading whitespace
sed 'G' # Double space file
sed '/^$/d' # Remove blank lines
sed 's/,/\t/g' # CSV to TSV
sed -n '1p' # Print first line (head -1)
sed -n '$p' # Print last line (tail -1)
-i for in-place editing carefully-i before modifying files| or # when dealing with paths-i.bak when editing in placeman sed - The manual pageinfo sed - GNU sed documentation