Search code examples
bashmacosdirectoryshbatch-rename

Trying to rename certain file types within recursive directories


I have a bunch of files within a directory structure as such:

Dir
    SubDir
            File
            File
    Subdir
            SubDir
                      File
            File
    File

Sorry for the messy formatting, but as you can see there are files at all different directory levels. All of these file names have a string of 7 numbers appended to them as such: 1234567_filename.ext. I am trying to remove the number and underscore at the start of the filename.

Right now I am using bash and using this oneliner to rename the files using mv and cut:

for i in *; do mv "$i" "$(echo $i | cut -d_ -f2-10)"; done

This is being run while I am CD'd into the directory. I would love to find a way to do this recursively, so that it only renamed files, not folders. I have also used a foreach loop in the shell, outside of bash for directories that have a bunch of folders with files in them and no other subdirectories as such:

foreach$ set p=`echo $f | cut -d/ -f1`
foreach$ set n=`echo $f | cut -d/ -f2 | cut -d_ -f2-10`
foreach$ mv $f $p/$n
foreach$ end

But that only works when there are no other subdirectories within the folders.

Is there a loop or oneliner I can use to rename all files within the directories? I even tried using find but couldn't figure out how to incorporate cut into the code.

Any help is much appreciated.


Solution

  • bash does provide functions, and these can be recursive, but you don't need a recursive function for this job. You just need to enumerate all the files in the tree. The find command can do that, but turning on bash's globstar option and using a shell glob to do it is safer:

    #!/bin/bash
    
    shopt -s globstar
    
    # enumerate all the files in the tree rooted at the current working directory
    for f in **; do
        # ignore directories
        test -d "$f" && continue
    
        # separate the base file name from the path
        name=$(basename "$f")
        dir=$(dirname "$f")
    
        # perform the rename, using a pattern substitution on the name part
        mv "$f" "${dir}/${name/#???????_/}"
    done
    

    Note that that does not verify that file names actually match the pattern you specified before performing the rename; I'm taking you at your word that they do. If such a check were wanted then it could certainly be added.