I use this command to rename files recursively:

find -iname \*.bak | rename 's/.bak/.old/'

But I want to exclude the current directory. Example:


After I want this:


2 Answers 2


With a find that supports the -mindepth predicate:

 find . -mindepth 2 -type f -name '*.bak' | rename 's/\.bak$/.old/'

The -mindepth predicate allows you to define the minimum search depth at which matches can be made. Depth zero is the root search path itself (.), depth 1 is anything immediately under the root search path (e.g. ./somename), and depth 2 is anything immediately below that (e.g. ./somename/other), etc.

Note that the caseinsensitive match that -iname does is not needed as your rename would not rename files that have other types of capitalization of the filename suffix anyway, so I've changed it to a -name test instead. I've also fixed your rename call so that files with the string bak elsewhere in the name, like rebak.bak, is not rename in strange ways (e.g. into r.old.bak).

I've also added -type f to only make find consider regular files, i.e. not directories etc.

For a more portable solution:

find . -mindepth 2 -type f -name '*.bak' -exec sh -c '
    for pathname do
        mv "$pathname" "${pathname%.bak}.old"
    done' sh {} +

This does not rely on the rename utilitiy.

find . -mindepth 2 -type f -name '*.bak' -exec rename 's/\.bak$/.old/' {} +

This calls rename directly from find.


With bash, you can skip find altogether and instead do

shopt -s globstar dotglob

rename 's/\.bak$/.old/' ./*/**/*.bak

The ** globbing pattern matches any number of intermediate subdirectories. This special pattern is enabled with the globstar shell option. The initial ./*/ makes sure that we only look in subdirectories of the current directory, and not in the current directory itself.

The dotglob shell option makes sure that we match hidden names with the pattern.

The above would however not pick out regular files exclusively (but neither does your find command). To do that, use a loop:

shopt -s globstar dotglob

for pathname in ./*/**/*.bak; do
    [[ ! -f $pathname ]] && continue
    rename 's/\.bak$/.old/' "$pathname"

This would also rename symbolic links to regular files.


Try this

find -iname \*.bak -not -path "./.bak" | rename 's/.bak/.old/'
  • This only excludes the specific pathname ./.bak, but not e.g. ./somefile.bak.
    – Kusalananda
    Feb 10, 2020 at 8:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .