I have this bash:

  find myDirectory/. -type f -print0 | xargs -0 sed -i $replace;

that will recursively scan myDirectory tree and replace all occurrences of AAAA with BBBB on the files there.

But I want to limit this to happen on files of specific extensions, for example, .txt, .read, .po

How do I impose this limit?


You can use the -name option for find to restrict matches based on filename.

find myDirectory/. -type f -name '*.txt' -print0 | xargs -0 sed -i "$replace"

For multiple extensions, you can use -o (or) and group them with ().

find myDirectory/. -type f \( -name '*.txt' -o -name '*.read' \) -print0 | xargs -0 sed -i "$replace"

Another improvement that can be made is using -exec instead of xargs. This is more portable and eliminates a subshell.

find myDirectory/. -type f -name '*.txt' -exec sed -i "$replace" {} +
  • 2nd option working for me... thanks!!! – Duck Jan 7 '13 at 2:27
  • 1
    For anyone googling, to run the last line in this answer without a variable, here is an example that recursively finds all txt files with foo and replaces with bar. Include the plus sign at the end: find ./ -type f -name '*.txt' -readable -writable -exec sed -i "s/foo/bar/g" {} + – degenerate Oct 20 '17 at 14:27

Add these settings to your .bashrc:

shopt -s extglob globstar

extglob turns on some additional patterns, including the @(…) construct for disjunction. globstar turns on **/ which traverses directories recursively.

Then you don't need to use find:

sed -i "$replace" mydirectory/**/*.@(txt|read|po)

In zsh, you don't need any special option, just run

sed -i $replace mydirectory/**/*.(txt|read|po)

If you have a lot of files, you may see a message like “command line length limit exceeded”. But the limit is very high on modern Linux systems, you're unlikely to encounter it.

  • I upvoted the question because I appreciate the globbing, but it seems overkill to ask someone to modify one's .bashrc for a single command. Why not just set it before the command, and optionally reset afterwards? If the user wants the setting permanently, that would be a separate issue. – user1404316 Jan 29 '18 at 15:28
  • Also, is extglob really necessary? Can't the same be accomplished with standard brace expansion, ie. {txt,read,po}instead of @(txt|read|po)? – user1404316 Jan 29 '18 at 18:17
  • @user1404316 Braces only work if there is at least one file with each extension. Otherwise the corresponding glob pattern will remain unchanged. E.g. if the current directory contains hello.world, foo.txt, bar.txt, messages.po only, then cat *.(txt|read|po) is equivalent to cat messages.po bar.txt foo.txt (single glob pattern, expands in lexicographic order) while cat *.{txt,read,po} expands to cat bar.txt foo.txt *.read messages.po and cat will complain that there is no file called *.read. – Gilles 'SO- stop being evil' Jan 29 '18 at 23:11

I tried this successfully -- to replace occurence of DBA with AOS

sed -i -E "s/DBA/AOS/g" *.sh
  • The question says recursively. – Scott Jan 29 '18 at 16:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.