bash scripting: batch file renamer

Summary

Bit by bit, I have replaced repetitive tasks completed using downloaded programs with scripts that do the same thing more efficiently, without the residual registry and file junk many programs leave, and with the flexibility to allow me to modify the script to suite specific needs in the future. This was the case with batch renaming files. I've included the script and briefly go over the code.

Bit by bit, I have replaced repetitive tasks completed using downloaded programs with scripts that do the same thing more efficiently, without the residual registry and file junk many programs leave, and with the flexibility to allow me to modify the script to suite specific needs in the future. This was the case with batch renaming files. I often had data from experiments or other projects that was in a regular, but undesirable, naming formats (thanks in part to industrial software designers and their penchant for meaningless output filenames). Changing either involved manually hitting F2 and changing the name (yuck!), downloading a program (lets avoid doing that...), or writing a cheap sed function.

I have finally decided to extend the use of sed and mv to a fully functional script for batch renaming files in a directory. I'll briefly comment on the code. A batch version (for Windows) may come in the future. Else, just download cygwin to use the bash shell environment in Windows, you'll be glad for it.

For those who want to dive right in, the script can be downloaded at:

batch renamer

The script is fairly basic, but I wanted it to be slightly flexible without getting bogged down in needless exception handling (for another time). To start, the script asks for the directory and changes to that if needed. A function to ask for user input is defined for use later. The internal field separator is changed to allow parsing of files and directories with spaces.

Bash
  1. #!/bin/bash
  2. # biafra ahanonu
  3. # 2013.02.20
  4. # Script to automate regexp replacement of filenames
  5. # Currently operates on all files in a directory
  6. # Features to add
  7.         # Type of file (.mp3, .txt, etc.)
  8.         # regexp of files to actually change
  9.  
  10. # Ask user for directory
  11. echo "Directory? "
  12. read userDir
  13. cd $userDir  
  14.  
  15. # Yes/No function
  16. getYesNo(){
  17.         select terminateSignal in "Yes" "No"
  18.         do
  19.                 case $terminateSignal in
  20.                         "Yes" )
  21.                                 return 1;;
  22.                         "No" )
  23.                                 return 0;;  
  24.                 esac
  25.         done   
  26. }
  27.  
  28. # Change file separator to allow use of files with spaces
  29. oldIFS=$IFS
  30. IFS=$(echo -en "\n\b")

Then it ask for the number of different regular expressions to run on the files in the directory. This is useful for two reasons. It allows one to change multiple different batches of files at once (assuming they have some difference in how they are named) or to do multiple different search and replace operations on the same file. Further, the method I use allows several steps of replacement. For example, say you want to remove all spaces, normally the end sed command (inside some file-replacing loop for function) would be sed 's/ /_/g', but I just ask for you to hit spacebar and underscore and you're on your way. In addition, the script confirms the regexp pattern is the one you want by displaying an example use on one file in the directory.

Bash
  1. # Ask user if multiple regexp are wanted
  2. echo "number of regexp's (e.g. 3)? "
  3. read regexpNum  
  4. echo "_______"
  5.  
  6. # Loop through until user has entered all regexp
  7. for (( i = 0; i < $regexpNum; i++ )); do
  8.         # Get regexp to replace
  9.         echo "Enter replacing regexp #"$i": "
  10.         read oldRegexp
  11.         # Get string to replace with
  12.         echo "Enter string #"$i" to replace with: "
  13.         read newRegexp
  14.  
  15.         # Store in an array
  16.         oldRegexpArray[$i]=$oldRegexp
  17.         newRegexpArray[$i]=$newRegexp
  18.  
  19.         # Check against one of the files in the directory
  20.         fileCheck=($(ls))
  21.         echo ${fileCheck[0]} | sed 's/'${oldRegexpArray[$i]}'/'${newRegexpArray[$i]}'/g'
  22.        
  23.         # If not desired regexp, repeat
  24.         getYesNo
  25.         response=$?
  26.         if [[ $response == 0 ]]; then
  27.                 i=$(expr $i - 1)
  28.         fi
  29.         echo "___"
  30. done

Further, the regular expressions can be chained, e.g. sed 's/ /_/g;s/foo/bar/g'. The semicolon allows multiple regular expressions to be used with sed without needing to pipe the commands. I accomplish this automatically from the multiple regular expressions and replacements using a simple loop.

Bash
  1. # Compile all expressions into one sed regexp
  2. sedRegexp=""
  3. for (( i = 0; i < ${#oldRegexpArray[*]}; i++ )); do
  4.         sedRegexp=$sedRegexp's/'${oldRegexpArray[$i]}'/'${newRegexpArray[$i]}'/g;'
  5. done
  6. # Remove trailing semicolon
  7. sedRegexpLen=${#sedRegexp}-1
  8. sedRegexp=${sedRegexp:0:sedRegexpLen}
  9. echo $sedRegexp
  10. echo "_______"

The rest of the script simply shows the user modified filenames and then renames the them after confirming those changes are desired. To save a bit of time and unnecessary warnings, I check that the old and new filename are actually different before proceeding.

Bash
  1. for oldfile in *
  2. do
  3.         newname=$(echo $oldfile | sed $sedRegexp)
  4.         echo "old: "$oldfile" | new: "$newname
  5. done
  6. echo "_______"
  7.  
  8. # Ask user if changes look good, if not, restart script
  9. echo "Continue with renaming? If NO, script with restart. "
  10. getYesNo
  11. response=$?
  12. if [[ $response == 0 ]]; then
  13.         bash batch_renamer.sh
  14. fi
  15. echo "_______"
  16.  
  17. # Rename files
  18. for oldfile in *
  19. do
  20.         newname=$(echo $oldfile | sed $sedRegexp)
  21.  
  22.         # If new and old name are the same, skip
  23.         if [[ $oldfile == $newname ]]; then
  24.                 continue
  25.         fi
  26.  
  27.         # Echo files being changed and change them
  28.         echo $oldfile $newname
  29.         mv $oldfile $newname
  30. done
  31.  
  32. # Return file separator to default
  33. IFS=$oldIFS

There are a couple things I would like to improve, such as ability to select specific files or choose the type (by extension) of file to change. The later will just involve integration with grep or find, but the former might be a little trickier if I want to stay within the confines of bash. Might play around with select, dialog, or just a loop asking for files from a list. Probably better if files are just organized and segregated properly...

Alright, thats all. The code was written in a dash and the post should be quick as well.

download batch_renamer.sh

Bash
  1. #!/bin/bash
  2. # biafra ahanonu
  3. # 2013.02.20
  4. # Script to automate regexp replacement of filenames
  5. # Currently operates on all files in a directory
  6. # Features to add
  7.         # Type of file (.mp3, .txt, etc.)
  8.         # regexp of files to actually change
  9.  
  10. # Ask user for directory
  11. echo "Directory? "
  12. read userDir
  13. cd $userDir  
  14.  
  15. # Yes/No function
  16. getYesNo(){
  17.         select terminateSignal in "Yes" "No"
  18.         do
  19.                 case $terminateSignal in
  20.                         "Yes" )
  21.                                 return 1;;
  22.                         "No" )
  23.                                 return 0;;  
  24.                 esac
  25.         done   
  26. }
  27.  
  28. # Change file separator to allow use of files with spaces
  29. oldIFS=$IFS
  30. IFS=$(echo -en "\n\b")
  31.  
  32. # Ask user if multiple regexp are wanted
  33. echo "number of regexp's (e.g. 3)? "
  34. read regexpNum  
  35. echo "_______"
  36.  
  37. # Loop through until user has entered all regexp
  38. for (( i = 0; i < $regexpNum; i++ )); do
  39.         # Get regexp to replace
  40.         echo "Enter replacing regexp #"$i": "
  41.         read oldRegexp
  42.         # Get string to replace with
  43.         echo "Enter string #"$i" to replace with: "
  44.         read newRegexp
  45.  
  46.         # Store in an array
  47.         oldRegexpArray[$i]=$oldRegexp
  48.         newRegexpArray[$i]=$newRegexp
  49.  
  50.         # Check against one of the files in the directory
  51.         fileCheck=($(ls))
  52.         echo ${fileCheck[0]} | sed 's/'${oldRegexpArray[$i]}'/'${newRegexpArray[$i]}'/g'
  53.        
  54.         # If not desired regexp, repeat
  55.         getYesNo
  56.         response=$?
  57.         if [[ $response == 0 ]]; then
  58.                 i=$(expr $i - 1)
  59.         fi
  60.         echo "___"
  61. done
  62.  
  63. # Compile all expressions into one sed regexp
  64. sedRegexp=""
  65. for (( i = 0; i < ${#oldRegexpArray[*]}; i++ )); do
  66.         sedRegexp=$sedRegexp's/'${oldRegexpArray[$i]}'/'${newRegexpArray[$i]}'/g;'
  67. done
  68. # Remove trailing semicolon
  69. sedRegexpLen=${#sedRegexp}-1
  70. sedRegexp=${sedRegexp:0:sedRegexpLen}
  71. echo $sedRegexp
  72. echo "_______"
  73.  
  74. # Ask user if changes look good, if not, restart script
  75. echo "Display results? "
  76. getYesNo
  77. displayResults=$?
  78.  
  79. if [[ $displayResults == 0 ]]; then
  80.         for oldfile in *
  81.         do
  82.                 newname=$(echo $oldfile | sed $sedRegexp)
  83.                         echo "old: "$oldfile" | new: "$newname
  84.         done
  85. fi
  86. echo "_______"
  87.  
  88. # Ask user if changes look good, if not, restart script
  89. echo "Continue with renaming? If NO, script with restart. "
  90. getYesNo
  91. response=$?
  92. if [[ $response == 0 ]]; then
  93.         bash batch_renamer.sh
  94. fi
  95. echo "_______"
  96.  
  97. # Rename files
  98. for oldfile in *
  99. do
  100.         newname=$(echo $oldfile | sed $sedRegexp)
  101.  
  102.         # If new and old name are the same, skip
  103.         if [[ $oldfile == $newname ]]; then
  104.                 continue
  105.         fi
  106.  
  107.         # Echo files being changed and change them
  108.         if [[ $displayResults == 0 ]]; then
  109.                 echo $oldfile $newname
  110.         fi
  111.         mv $oldfile $newname
  112. done
  113.  
  114. # Return file separator to default
  115. IFS=$oldIFS

-biafra
bahanonu [at] alum.mit.edu

©2006-2024 | Site created & coded by Biafra Ahanonu | Updated 21 October 2024
biafra ahanonu