recursive find/replace
awk
sed
string manipulation
command line tools

How can I do a recursive find/replace of a string with awk or sed?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Recursive find and replace is usually a two-part job: one tool walks the directory tree, and another tool edits file contents. In practice, find does the traversal, while sed or awk performs the replacement. The safest solution depends on whether you need a simple literal substitution or more structured line-by-line logic.

Use find to Select the Right Files

Neither sed nor awk recursively scans directories on its own. Start by using find to choose only the files you actually want to edit. That matters because a careless command can touch generated files, dependencies, or binary assets.

For a straightforward replacement across Markdown files:

bash
find . -type f -name '*.md' -exec sed -i.bak 's/old-text/new-text/g' {} +

This command means:

  • 'find . starts in the current directory.'
  • '-type f limits the search to regular files.'
  • '-name '*.md' narrows the match to Markdown files.'
  • '-exec ... {} + passes batches of matching files to sed.'
  • '-i.bak edits files in place and keeps a backup with the .bak suffix.'

Using a backup suffix is a good default when you are updating many files. If the result is wrong, you can restore from the backup instead of trying to undo a large edit manually.

Recursive Replacement with sed

sed is the simplest choice when the job is "replace one pattern with another everywhere." The s command performs substitution, and the trailing g flag means "replace every match on the line."

Here is a realistic example that updates an old API host in all shell scripts:

bash
find scripts -type f -name '*.sh' -exec \
  sed -i.bak 's#http://old-api.internal#https://api.internal#g' {} +

The # delimiter is intentional. It avoids excessive escaping when the text contains forward slashes.

If your filenames may contain spaces, tabs, or newlines, use null-delimited input:

bash
find . -type f -name '*.txt' -print0 |
  xargs -0 sed -i.bak 's/hello/world/g'

On macOS, BSD sed handles -i a little differently. An empty backup suffix is written as -i '', while GNU sed accepts plain -i. The portable option is to keep a real suffix such as .bak.

When awk Is the Better Fit

awk is more useful when replacement rules depend on fields, conditions, or line structure. It can still do global substitution with gsub, but it also lets you skip lines, inspect columns, or rewrite only part of a file.

Suppose you want to update a port number only in configuration lines that start with upstream=:

bash
1find config -type f -name '*.conf' -print0 |
2while IFS= read -r -d '' file; do
3  awk '
4    /^upstream=/ {
5      gsub(/:8080/, ":8443")
6    }
7    { print }
8  ' "$file" > "$file.tmp" &&
9  mv "$file.tmp" "$file"
10done

This is more expressive than sed because the replacement happens only on lines that match ^upstream=. The rest of the file passes through unchanged.

Unlike sed -i, standard awk does not edit files in place. The usual pattern is:

  1. Read the original file.
  2. Write transformed output to a temporary file.
  3. Move the temporary file over the original.

That extra step is worth it when the rule is more complex than a basic text substitution.

Choosing Between sed and awk

Use sed when:

  • the replacement is simple
  • every matching line should be treated the same way
  • in-place editing is convenient

Use awk when:

  • the replacement depends on fields or patterns
  • only certain lines should change
  • you need variables, conditions, or counters

For example, replacing foo with bar across hundreds of source files is a classic sed task. Updating only the second column of lines that match a condition is much more naturally expressed in awk.

Common Pitfalls

The most common mistake is editing the wrong files. Restrict find with -name, -path, or -not -path so you do not rewrite files under directories such as node_modules, .git, or build output.

Another frequent issue is treating sed -i as fully portable. GNU and BSD sed differ, so commands copied from Linux often fail on macOS. Using -i.bak is safer across systems.

Pattern syntax also causes trouble. Characters such as ., *, [, ], and & have special meaning in regular expressions or replacement strings. If you mean a literal value, escape it carefully or choose a different delimiter.

Finally, avoid running these commands blindly on binary files. Recursive replacement is meant for text. Applying it to images, archives, or compiled artifacts can corrupt them.

Summary

  • Recursive replacement is usually find plus sed or awk, not sed or awk alone.
  • 'sed is best for simple global substitutions across many files.'
  • 'awk is better when the replacement depends on line content or field structure.'
  • Use backup files and narrow file selection before editing in place.
  • Prefer null-delimited pipelines when filenames may contain spaces or unusual characters.

Course illustration
Course illustration

All Rights Reserved.