Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash script to rename folders recursively

I want to rename all my folders and folders within these folders, so that all underscores in the folder name are replaced by spaces. Can you help me with that?

like image 845
user1037704 Avatar asked Nov 09 '11 13:11

user1037704


2 Answers

The answer that uses -execdir is simpler, but will only work with versions of find that support -execdir such as GNU find, since POSIX only specifies -exec.

If you just want to use bash, this is surprisingly tricky to get right, since you're renaming the directories that find is searching through. Even if you get the order right, by using the -depth option to find, you need to make sure you only rewrite the last component of the path with each -exec. The following is a trivial variation of one of the examples given in the Bash FAQ and seems to work OK for me:

find . -depth -name "*_*" -exec bash -c 'dir=${1%/*} base=${1##*/}; mv "$1" "$dir/${base//_/ }"' _ {} \;

That FAQ answer has more discussion about problem of recursively naming folders which may be of interest.


Update: Since that one-liner is rather complex, it might be worth breaking it down a little in the interests of explanation. Essentially, the find command is:

find . -depth -name "*_*" -exec bash -c [SOME-STUFF] _ {} \;

In other words, find all the directories which contain an underscore, and for each such directory, starting with the deepest, run the bash script [SOME-STUFF], with parameter 0 as _ (to indicate that we don't care about it) and parameter 1 as the name of the file that find found. (find will substitute the filename for {} after -exec. The \; just terminates the command that -exec runs.)

Then the [SOME-STUFF] part is made up of:

dir=${1%/*}

... which, using parameter expansion, will remove the shortest match for /* from the end of $1 (the filename) and set dir to the result. Similarly, this part:

base=${1##*/}

... removes the longest match of */ from the start of $1 and sets base to the result. So base is just the last component of the path.

Then the renaming is actually done by the mv command, which is:

mv "$1" "$dir/${base//_/ }"

That again uses parameter expansion, this time with the ${parameter/pattern/string} syntax. The filename ($1) is renamed to $dir followed by $base but with each underscore in $base replaced with a space.

like image 185
Mark Longair Avatar answered Sep 28 '22 15:09

Mark Longair


Yes, simply cd to /dir and :

find -depth -type d -execdir rename 's/_/ /g' {} \;

Depending of the distro, you need perl rename (sometimes prename).

If

file $(type -p rename)

output contains ELF, that looks bad ;)

edit -depth and -execdir added

like image 30
Gilles Quenot Avatar answered Sep 28 '22 15:09

Gilles Quenot