Meet your new friend sed. This amazingly powerful terminal tool is here to be totally underused for things like finding and replacing strings in files.

Update a string in multiple files with sed

You’ve got two levels of intensity to choose from:

  • Non-recursive: Just the files in my current directory.
  • Recursive: This directory and all the subdirectories it contains, with maximum prejudice.

Here’s how!

Current directory, non-recursive

Non-recursive means sed won’t change files in any subdirectories of the current folder.

.
├── index.html        # Change this file
└── blog
    ├── list.html     # Don't change
    └── single.html   # these files

Run this command to search all the files in your current directory and replace a given string. For example, to replace all occurrences of “foo” with “bar”:

sed -i -- 's/foo/bar/g' *

Here’s what each component of the command does:

  • -i will change the original, and stands for “in-place.”
  • s is for substitute, so we can find and replace.
  • foo is the string we’ll be taking away,
  • bar is the string we’ll use instead today.
  • g as in “global” means “all occurrences, please.”
  • * denotes all file types. (No more rhymes. What a tease.)

You can limit the operation to one file type, such as txt, by using a matching pattern:

sed -i -- 's/foo/bar/g' *.txt

Current directory and subdirectories, recursive

You can supplement sed with find to expand your scope to all of the current folder’s subdirectories. This will include any hidden files.

find . -type f -exec sed -i 's/foo/bar/g' {} +

To ignore hidden files (such as .git) you can pass the negation modifier -not -path '*/\.*', like this:

find . -type f -not -path '*/\.*' -exec sed -i 's/foo/bar/g' {} +

This will exclude any file that has the string /. in its path.

You can also limit this operation to file names that end in a certain extension, like Markdown:

find . -type f -name "*.md" -exec sed -i 's/foo/bar/g' {} +

Replacing URLs: change the separator

If you want to update a URL, the / separator in your strings will need escaping. It ends up looking like this…

find . -type f -exec sed -i \
's/https:\/\/www.oldurl.com\/blog/https:\/\/www.newurl.com\/blog/g' {} +

You can avoid confusion and mistakes by changing the separator to any non-conflicting character. The character that follows the s will be treated as the separator. In this case, using a , or _ would do. This doesn’t require escaping and is much more readable:

find . -type f -exec sed -i \
's_https://www.oldurl.com/blog_https://www.newurl.com/blog_g' {} +

I write about time-saving terminal tricks and how to improve productivity as a software developer. You can get these tips right in your inbox by signing up below!