Home > other >  How to rename files recursively with Bash
How to rename files recursively with Bash

Time:07-09

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: ./directory-pattern: No such file or directory or rename: ./directory-pattern/another-expression: not accessible: No such file or directory.

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?

CodePudding user response:

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.

  •  Tags:  
  • bash
  • Related