Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to rename files recursively with Bash

I am trying to make a bash script that should replace any occurrence of a given pattern with an other, given, expression in any path in a given directory. For instance, if I have the following tree structure:

.
|- file1
|- file-pattern-pattern.html
|- directory-pattern/
|  |- another-pattern
|  \- pattern.pattern
\- other-pattern/
   \- a-file-pattern

it should end up looking like

.
|- file1
|- file-expression-expression.html
|- directory-expression/
|  |- another-expression
|  \- expression.expression
\- other-expression/
   \- a-file-expression

The main issue I have is that most solution I have found make either usage of the ** glob pattern alongside with a shopt -s globstar nullglob or find to execute rename on all the files, but since I actually change the name of a directory during that operation, it breaks with messages like find: ./direction-pattern: No such file or directory or rename: ./direction-pattern/another-expression: not accessible: No such file or directory.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

The second issue is that, according to rename‘a man page, it will rename the specified files by repalcing the first occurrence of the pattern, not all occurrences, and I didn’t find any option to overwrite this behavior. Of course, I don’t want to "just run rename with -v until it doesn’t spit anything anymore", which just sounds silly.

So the question is: how do I achieve that bulk-renaming in Bash?

>Solution :

Edit: leave only the 1-pass solution that apparently works as well as the 2-passes.

You’ll probably have to explore the hierarchy depth first. Example with find and a bash exec script:

$ find . -depth -name '*pattern*' -exec bash -c \
  'f=$(basename "$1"); d=$(dirname "$1"); \
   mv "$1" "$d/${f//pattern/expression}"' _ {} \;

Demo:

$ tree .
.
├── file-pattern-pattern.html
├── file1
├── foo-pattern
│   └── directory-pattern
│       ├── another-pattern
│       └── pattern.pattern
└── other-pattern
    └── a-file-pattern

$ find . -depth -name '*pattern*' -exec bash -c \
  'f=$(basename "$1"); d=$(dirname "$1"); \
   mv "$1" "$d/${f//pattern/expression}"' _ {} \;

$ tree .
.
├── file-expression-expression.html
├── file1
├── foo-expression
│   └── directory-expression
│       ├── another-expression
│       └── expression.expression
└── other-expression
    └── a-file-expression

Explanation: -depth tells find to process each directory’s contents before the directory itself. This avoids one of the issues you encountered when referring to a directory that was already renamed. The bash script uses simple pattern substitutions to replace all occurrences of string pattern by string expression.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading