65

I want to rename all files and directories that contain the word "special" to "regular". It should maintain case sensitivity so "Special" won't become "regular".

How can i do this in bash recursively?

Tom
  • 9,275
  • 25
  • 89
  • 147

8 Answers8

117

A solution using find:

To rename files only:

find /your/target/path/ -type f -exec rename 's/special/regular/' '{}' \;

To rename directories only:

find /your/target/path/ -type d -execdir rename 's/special/regular/' '{}' \+

To rename both files and directories:

find /your/target/path/ -execdir rename 's/special/regular/' '{}' \+
speakr
  • 4,141
  • 1
  • 22
  • 28
  • 4
    @speakr, Great solution, thanks! Could you provide more details about what it is doing, especially this part " '{}' \ "? Quick goggling did not help, so I thought that this information may be useful for others as well. – Dmitry Gonchar Feb 27 '15 at 13:21
  • 2
    @DmitryGonchar Just take a look at `-exec command ;` in the [_find_ manpage](http://linux.die.net/man/1/find). – speakr Feb 27 '15 at 14:09
  • I tried this with the -n flag (not ready to experiment with permanet results yet, and got this error: Replacement list is longer than search list at (eval 1) line 1. My actual command invoked was: find $HOME -type f -exec prename -n 'y/ /-_-/' '{}' \; – Dennis Sep 20 '15 at 01:06
  • 2
    @ThiloSchulz It does, but you have to use `find … -execdir … '{}' +` instead of `… '{}' \;`. Clarification added. – speakr Jan 16 '17 at 10:20
  • @speakr I'm not getting the results I'm expecting for replacing directories, and I'm not sure I'm understanding the 'find ... -execdir suggestion. Do I change type f to type d? Can you please write out the full working command for directories in your answer please? – user658182 Jan 28 '17 at 18:43
  • @user658182 Yes, use `-type d` combined with `-execdir` for directories only. – speakr Sep 06 '17 at 09:43
  • 2
    In case of many files, take advantage of regex in find, for the last example above you use: ``find /your/target/path/ -regex '.*special.*' -execdir rename 's/special/regular/' '{}' \+``, otherwise it will be quite slow! – Rui Seixas Monteiro Jan 25 '18 at 13:44
  • 1
    great, thank you. However, does not seem to do the job `recursively` on OSX.... – Cmag May 04 '20 at 15:42
89

Try doing this (require bash --version >= 4):

shopt -s globstar
rename -n 's/special/regular/' **

Remove the -n switch when your tests are OK

warning There are other tools with the same name which may or may not be able to do this, so be careful.

If you run the following command (GNU)

$ file "$(readlink -f "$(type -p rename)")"

and you have a result like

.../rename: Perl script, ASCII text executable

and not containing:

ELF

then this seems to be the right tool =)

If not, to make it the default (usually already the case) on Debian and derivative like Ubuntu :

$ sudo update-alternatives --set rename /path/to/rename

(replace /path/to/rename to the path of your perl's rename command.


If you don't have this command, search your package manager to install it or do it manually


Last but not least, this tool was originally written by Larry Wall, the Perl's dad.

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • 9
    what is the `shopt -s globstar` for? – Tom Feb 21 '13 at 21:40
  • 4
    It enables the bash feature `**` (stands for recursive) maybe already enabled. – Gilles Quénot Feb 21 '13 at 21:41
  • GNU bash, version 4.2.24(1)-release – Tom Feb 21 '13 at 22:04
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/24933/discussion-between-sputnick-and-tom) – Gilles Quénot Feb 21 '13 at 22:22
  • The chat window you linked to is borked..I just try this and I got this response: rename: invalid option -- 'n' version: [##@localhost Staticality]$ bash --version GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu) – killjoy Jul 01 '15 at 19:16
  • 4
    I have about 1700 files to rename in directories in my home directory. That seems too many for this to work, that actually was the text in the error message, too many arguments. – Dennis Sep 20 '15 at 00:57
  • @killjoy: you have to install perl's rename. (sometimes named `prename` or install the module: `cpan File::Rename` @Dennis: use find : `find . -exec rename 's/special/regular/' {} +` – Gilles Quénot Sep 20 '15 at 11:27
  • Does it work recursively in the current directory or globally? In other words, is the path "." or "/"? – user1 Jun 20 '16 at 06:45
  • For me this causes `-bash: /usr/bin/rename: Argument list too long` – AnnanFay May 24 '17 at 22:59
  • So use `find /dir -exec rename 's///' {} +` – Gilles Quénot May 25 '17 at 08:34
  • You may have to run the rename command multiple times in a row: run it until it doesn't report moved files. – Brannon Sep 29 '17 at 16:44
  • -_- Are you sure you have perl's rename before 'downvoting' ? – Gilles Quénot Nov 19 '17 at 18:27
  • 1
    in order to avoid errors use for i in **; do rename 's///' "$i"; done after globstar – Ferroao Dec 02 '17 at 21:21
  • 1
    People trying to rename a pattern occuring multiple times in the same name should add the 'g' flag at the end of the substitution command: `'s/old/new/g'` – Romain Vincent Feb 04 '18 at 16:09
7

If you don't mind installing another tool, then you can use rnm:

rnm -rs '/special/regular/g' -dp -1 *

It will go through all directories/sub-directories (because of -dp -1) and replace special with regular in their names.

Jahid
  • 21,542
  • 10
  • 90
  • 108
  • Is this installed from Homebrew? – LokMac Feb 11 '17 at 06:07
  • @LokMac : I don't think it's available in homebrew (I only package binary for debian based OS), If you have automake, you can install it in any Unix system (Instructions are in the [readme](https://github.com/neurobin/rnm/blob/release/README.md#install-from-source)). – Jahid Feb 11 '17 at 12:39
  • Great tool! Worked for me! – Black Jun 23 '17 at 07:54
6

@speakr's answer was the clue for me.

If using -execdir to transform both files and directories, you'll also want to remove -type f from the example shown. To spell it out, use:

find /your/target/path/ -execdir rename 's/special/regular/' '{}' \+

Also, consider adding g (global) flag to the regex if you want to replace all occurrences of special with regular in a given filename and not just the first occurrence. For example:

find /your/target/path/ -execdir rename 's/special/regular/g' '{}' \+

will transform special-special.jpg to regular-regular.jpg. Without the global flag, you'll end up with regular-special.jpg.

FYI: GNU Rename is not installed by default on Mac OSX. If you are using the Homebrew package manager, brew install rename will remedy this.

Community
  • 1
  • 1
U007D
  • 5,772
  • 2
  • 38
  • 44
5

Here is another approach which is more portable and does not rely on the rename command (since it may require different parameters depending on the distros).

It renames files and directories recursively:

find . -depth -name "*special*" | \
while IFS= read -r ent; do mv $ent ${ent%special*}regular${ent##*special}; done

What it does

  • use find with -depth parameter to reorder the results by performing a depth-first traversal (i.e. all entries in a directory are displayed before the directory itself).
  • do pattern substitutions to only modifiy the last occurence of regular in the path.

That way the files are modified first and then each parent directory.

Example

Giving the following tree:

├── aa-special-aa
│   └── bb-special
│       ├── special-cc
│       ├── special-dd
│       └── Special-ee
└── special-00

It generate those mv commands in that particular order:

mv ./aa-special-aa/bb-special/special-cc ./aa-special-aa/bb-special/regular-cc
mv ./aa-special-aa/bb-special/special-dd ./aa-special-aa/bb-special/regular-dd
mv ./aa-special-aa/bb-special ./aa-special-aa/bb-regular
mv ./aa-special-aa ./aa-regular-aa
mv ./special-00 ./regular-00

To obtain the following tree:

├── aa-regular-aa
│   └── bb-regular
│       ├── regular-cc
│       ├── regular-dd
│       └── Special-ee
└── regular-00
jyvet
  • 2,021
  • 15
  • 22
3

As mentioned by Rui Seixas Monteiro it's best to use the -iregex pattern option with the Find command. I've found the following works and includes the global flag in the regex as mentioned by U007D:

Files:

find /path/ -type f -iregex '.*special.*' -execdir rename 's/special/regular/g' '{}' \+;

Directories:

find /path/ -type d -iregex '.*special.*' -execdir rename 's/special/regular/g' '{}' \+;

Files and Directories

find /path/ -iregex '.*special.*' -execdir rename 's/special/regular/g' '{}' \+;
Dig
  • 261
  • 2
  • 3
  • But it doesn't do a depth first traversal so it's open to problems with parent directories haven't been renamed first. – MrR Apr 26 '22 at 14:57
2

For those just wanting to rename directories you can use this command:

find /your/target/path/ -type d -execdir rename 's/special/regular/' '{}' \;

Note type is now d for directory, and using -execdir.

I haven't been able to work out how to rename both files and directories in a single pass though.

Someone commented earlier that once it renamed the root folder then it couldn't traverse the file tree any more. There is a -d switch available that does a depth traversal from the bottom-up, so the root would be renamed last I believe:

find -d /your/target/path/ -type d -execdir rename 's/special/regular/' '{}' \;

From the manpage (man find):

 -d      Cause find to perform a depth-first traversal, i.e., directories are visited in post-order and all entries in a directory will be
         acted on before the directory itself.  By default, find visits directories in pre-order, i.e., before their contents.  Note, the
         default is not a breadth-first traversal.
LokMac
  • 391
  • 2
  • 8
  • 17
  • If anyone can clarify what is done to action the command for both files and directories in a single pass that would be appreciated too, and I'll update this answer with the command. – LokMac Feb 11 '17 at 04:31
1

For rename version rename from util-linux 2.23.2 the following command worked for me:

find . -type f -exec rename mariadb mariadb-proxy '{}' \;

rok
  • 9,403
  • 17
  • 70
  • 126