Search code examples
bashfile-renamebatch-rename

Find and rename multiple files using a bash script in Linux


As an example, in a directory /home/hel/files/ are thousends of files and hundreds of directories. An application saves there its output files with special characters in the file names. I want to replace these special characters with underscores in all file names. e.g. -:"<>@

I wrote a bash script which simply repeats a command to rename the files using Linux/Unix 'rename'.

Example: file name: rename.sh

#!/bin/bash
rename "s/\'/_/g" *
rename 's/[-:"<>@\,&\s\(\)\[\]?!–~%„“;│\´\’\+#]/_/g' *
rename 'y/A-Z/a-z/' *
rename 's/\.(?=[^.]*\.)/_/g' *
rename 's/[_]{2,}/_/g' *

I execute the following find command:

find /home/hel/files/ -maxdepth 1 -type f -execdir /home/hel/scripts/rename.sh {} \+

Now the issue:

This works fine, except the fact, that it renames subdirectories too, if they have the searched characters in their name.

The find command searches just for files and not for directories.

I tried some other find variations like:

find /home/hel/files/ -maxdepth 1 -type f -execdir sh /home/hel/scripts/rename.sh {} \+
find /home/hel/files/ -maxdepth 1 -type f -execdir sh /home/hel/scripts/rename.sh {} +
find /home/hel/files/ -maxdepth 1 -type f -execdir sh /home/hel/scripts/rename.sh {} \;

They are all working, but with the same result.

What is not working:

find /home/hel/files/ -maxdepth 1 -type f -exec sh /home/hel/scripts/rename.sh {} \+

This one is dangerous, because it renames the directories and files in the current directory, where you call the find command too.

Maybe one has an idea, why this happens or has a better solution.


Solution

  • The script rename.sh did not use its command line arguments at all, but instead searched files and directories (!) on its own using the glob *.

    Change your script to the following.

    #!/bin/bash
    rename -d s/\''/_/g;
    s/[-:"<>@\,&\s\(\)\[\]?!–~%„“;│\´\’\+#]/_/g;
    y/A-Z/a-z/;
    s/\.(?=[^.]*\.)/_/g;
    s/[_]{2,}/_/g' "$@"
    

    Then use find ... -maxdepth 1 -type f -exec sh .../rename.sh {} +.

    Changes Made

    • Use "$@" instead of * to process the files given as arguments rather than everything in the current directory.
    • Execute rename only once as a 2nd rename wouldn't find the files specified with "$@" after they were renamed by the 1st rename.
    • Use the -d option such that only the basenames are modified. find always puts a path in front of the files, at the very least ./. Without this option rename would change ./filename to mangledPath/newFilename and therefore move the file to another directory.

    Note that man rename is a bit misleading

    --path, --fullpath
            Rename full path: including any directory component.  DEFAULT
    
    -d, --filename, --nopath, --nofullpath
            Do not rename directory: only rename filename component of path.
    

    For a given path rename -d 's...' some/path/basename just processes the basename and ignores the leading components some/path/. If basename is a directory it will still be renamed despite the -d option.