You have a configuration file with a hundred lines and you need to change one IP address. Or a log file with ten thousand lines and you need to find every “ERROR” from the last hour. Opening an editor is the slow way. grep finds what you are looking for; sed changes it. The danger is that sed can rewrite a file in place with no undo, so the real skill is knowing how to verify before you commit. This guide covers both commands, how to combine them safely, and the mistakes that turn a quick edit into data loss.

grep searches text for patterns. sed edits text from the command line. Together they are the fastest way to find and change strings across files without opening an editor, but only if you know how to preview changes before committing them.

I start every sed session the same way: preview first, then commit. sed without -i prints what would change to standard output without touching the file. That preview step is the habit that prevents data loss. Only after I confirm the output looks right do I add -i (or -i.bak for a safety net) to write the result back.

grep: find lines matching a pattern

grep prints every line that matches a pattern. It reads files or standard input. The pattern is a regular expression by default. I use grep when I need to answer a question about what is in a file: “Does this log contain errors?” “Which config files reference localhost?” “How many lines match this pattern?”

# Lines containing "error" in a log file
grep "error" app.log

# Case-insensitive search
grep -i "error" app.log

# Search recursively in a directory
grep -r "error" /var/log/

# Show line numbers
grep -n "error" app.log

# Count matching lines
grep -c "error" app.log

# List only filenames that contain the pattern
grep -l "error" *.log

# Invert match: show lines that do NOT contain "error"
grep -v "error" app.log

-r searches directories recursively. -n adds line numbers. -c counts instead of printing. -l lists filenames only, useful when you want the files, not the lines. -v inverts the match.

For multiple patterns, use -E (extended regex) so you can use | for “or”:

# Lines containing error OR fail OR critical
grep -E "error|fail|critical" app.log

-l is particularly useful as a stepping stone to sed. You can use grep -l to find which files contain a string, then feed those filenames to sed to transform them. That two-step pattern, find first, edit second — is safer than editing blindly because you see the file list before any changes happen.

sed: the stream editor

sed reads text line by line and applies commands to each line. The most common command is s (substitute). By default, sed prints every line to standard output. It does not change the file. That read-only default is important: you can run sed to see what would happen, decide whether the result looks right, and only then commit the change.

# Replace "old" with "new" on every line (first occurrence only)
sed 's/old/new/' file.txt

# Replace every occurrence on each line
sed 's/old/new/g' file.txt

# Replace only on line 5
sed '5s/old/new/g' file.txt

# Replace only on lines matching a pattern
sed '/pattern/s/old/new/g' file.txt

The s command means substitute: s/find/replace/flags. The delimiter does not have to be / — any character works, which helps when your text contains slashes:

# Avoid escaping slashes in paths
sed 's|/usr/local|/opt|g' config.txt
sed 's,http://,https://,g' urls.txt

The g flag controls scope. Without it, sed ‘s/old/new/’ replaces only the first occurrence on each line. Add g to replace every occurrence. I always ask myself: “Do I want the first occurrence only, or every occurrence on the line?” The answer depends on what the text represents: a config key that appears once per line versus a URL that might appear multiple times.

To see what sed would do without changing anything, run it without -i:

# Preview: see what would change
sed 's/localhost/127.0.0.1/g' *.conf

# Commit: write the result back to the file
sed -i 's/localhost/127.0.0.1/g' *.conf

-i edits the file in place. There is no undo. Always preview first. This is the single most important safety rule with sed.

Safe in-place editing

If you want a safety net, use -i.bak. sed writes the result to the original file and keeps a backup with the extension you specify:

# Edit in place, keep backup as config.txt.bak
sed -i.bak 's/old/new/g' config.txt

# Remove the backup when you are satisfied
rm config.txt.bak

This is the habit that prevents data loss: preview, then edit with a backup, then remove the backup when you are sure. I keep the backup until I have verified the change works. For a config file, that means restarting the service or reloading the configuration and confirming it behaves as expected.

Combining grep and sed

grep and sed work together naturally: grep selects the files or lines, sed transforms them. A common pattern is to find files containing a string, then replace that string in those files.

# Find files containing "localhost", then replace it in those files
for f in $(grep -l "localhost" *.conf); do
    sed -i 's/localhost/127.0.0.1/g' "$f"
done

Quote "$f" to handle filenames with spaces. Without quotes, a file named my config.conf breaks the loop. I always quote shell variables in loops like this. It is a small habit that prevents a class of bugs that are hard to debug once they happen.

You can also use grep’s exit status in shell scripts. grep -q produces no output; it only sets the exit code:

# Use grep in a conditional
if grep -q "PasswordAuthentication no" /etc/ssh/sshd_config; then
    echo "Password authentication is disabled"
else
    echo "WARNING: Password authentication may be enabled"
fi

grep -q runs silently and sets the exit code: 0 if it found a match, 1 if it did not. That makes it perfect for shell script conditionals. You do not need to parse output; you just need a boolean you can act on.

Useful sed recipes

TaskCommand
Delete all blank linessed '/^$/d' file.txt
Show lines 10 through 20sed -n '10,20p' file.txt
Delete lines from pattern START to pattern ENDsed '/^BEGIN/,/^END/d' file.txt
Strip HTML tagssed 's/<[^>]*>//g' file.txt
Strip trailing whitespacesed 's/[[:space:]]*$//' file.txt
Uncomment a line in a config filesed -i 's/^#\(PermitRootLogin\)/\1/' /etc/ssh/sshd_config

-n suppresses automatic printing; p prints only the lines you ask for. That is how sed -n ‘10,20p’ shows a range without printing the rest of the file. I use -n when I only need to inspect a specific section of a large file, it keeps the output focused and readable.

Common mistakes

  • Running sed -i without previewing. You cannot undo an in-place edit. Always run without -i first to verify the output.
  • Forgetting the g flag. sed 's/old/new/' replaces only the first occurrence on each line. Add g to replace every occurrence.
  • Not quoting variables in loops. for f in $(grep -l ...) breaks on filenames with spaces. Quote "$f".
  • Assuming grep finds everything. grep searches line by line. It does not match patterns that span multiple lines.

The questions worth asking

  • Did you preview the sed output before adding -i?
  • Do you need g to replace every occurrence, or just the first per line?
  • Are you quoting filenames in loops to handle spaces?
  • Are you using grep -l for filenames or grep -n for line numbers?

References

Related Tools

Try these companion tools on DevStackIO Tools: