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.
- #!/bin/bash
- # biafra ahanonu
- # 2013.02.20
- # Script to automate regexp replacement of filenames
- # Currently operates on all files in a directory
- # Features to add
- # Type of file (.mp3, .txt, etc.)
- # regexp of files to actually change
- # Ask user for directory
- echo "Directory? "
- read userDir
- cd $userDir
- # Yes/No function
- getYesNo(){
- select terminateSignal in "Yes" "No"
- do
- case $terminateSignal in
- "Yes" )
- return 1;;
- "No" )
- return 0;;
- esac
- done
- }
- # Change file separator to allow use of files with spaces
- oldIFS=$IFS
- 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.
- # Ask user if multiple regexp are wanted
- echo "number of regexp's (e.g. 3)? "
- read regexpNum
- echo "_______"
- # Loop through until user has entered all regexp
- for (( i = 0; i < $regexpNum; i++ )); do
- # Get regexp to replace
- echo "Enter replacing regexp #"$i": "
- read oldRegexp
- # Get string to replace with
- echo "Enter string #"$i" to replace with: "
- read newRegexp
- # Store in an array
- oldRegexpArray[$i]=$oldRegexp
- newRegexpArray[$i]=$newRegexp
- # Check against one of the files in the directory
- fileCheck=($(ls))
- echo ${fileCheck[0]} | sed 's/'${oldRegexpArray[$i]}'/'${newRegexpArray[$i]}'/g'
- # If not desired regexp, repeat
- getYesNo
- response=$?
- if [[ $response == 0 ]]; then
- i=$(expr $i - 1)
- fi
- echo "___"
- 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.
- # Compile all expressions into one sed regexp
- sedRegexp=""
- for (( i = 0; i < ${#oldRegexpArray[*]}; i++ )); do
- sedRegexp=$sedRegexp's/'${oldRegexpArray[$i]}'/'${newRegexpArray[$i]}'/g;'
- done
- # Remove trailing semicolon
- sedRegexpLen=${#sedRegexp}-1
- sedRegexp=${sedRegexp:0:sedRegexpLen}
- echo $sedRegexp
- 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.
- for oldfile in *
- do
- newname=$(echo $oldfile | sed $sedRegexp)
- echo "old: "$oldfile" | new: "$newname
- done
- echo "_______"
- # Ask user if changes look good, if not, restart script
- echo "Continue with renaming? If NO, script with restart. "
- getYesNo
- response=$?
- if [[ $response == 0 ]]; then
- bash batch_renamer.sh
- fi
- echo "_______"
- # Rename files
- for oldfile in *
- do
- newname=$(echo $oldfile | sed $sedRegexp)
- # If new and old name are the same, skip
- if [[ $oldfile == $newname ]]; then
- continue
- fi
- # Echo files being changed and change them
- echo $oldfile $newname
- mv $oldfile $newname
- done
- # Return file separator to default
- 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.
- #!/bin/bash
- # biafra ahanonu
- # 2013.02.20
- # Script to automate regexp replacement of filenames
- # Currently operates on all files in a directory
- # Features to add
- # Type of file (.mp3, .txt, etc.)
- # regexp of files to actually change
- # Ask user for directory
- echo "Directory? "
- read userDir
- cd $userDir
- # Yes/No function
- getYesNo(){
- select terminateSignal in "Yes" "No"
- do
- case $terminateSignal in
- "Yes" )
- return 1;;
- "No" )
- return 0;;
- esac
- done
- }
- # Change file separator to allow use of files with spaces
- oldIFS=$IFS
- IFS=$(echo -en "\n\b")
- # Ask user if multiple regexp are wanted
- echo "number of regexp's (e.g. 3)? "
- read regexpNum
- echo "_______"
- # Loop through until user has entered all regexp
- for (( i = 0; i < $regexpNum; i++ )); do
- # Get regexp to replace
- echo "Enter replacing regexp #"$i": "
- read oldRegexp
- # Get string to replace with
- echo "Enter string #"$i" to replace with: "
- read newRegexp
- # Store in an array
- oldRegexpArray[$i]=$oldRegexp
- newRegexpArray[$i]=$newRegexp
- # Check against one of the files in the directory
- fileCheck=($(ls))
- echo ${fileCheck[0]} | sed 's/'${oldRegexpArray[$i]}'/'${newRegexpArray[$i]}'/g'
- # If not desired regexp, repeat
- getYesNo
- response=$?
- if [[ $response == 0 ]]; then
- i=$(expr $i - 1)
- fi
- echo "___"
- done
- # Compile all expressions into one sed regexp
- sedRegexp=""
- for (( i = 0; i < ${#oldRegexpArray[*]}; i++ )); do
- sedRegexp=$sedRegexp's/'${oldRegexpArray[$i]}'/'${newRegexpArray[$i]}'/g;'
- done
- # Remove trailing semicolon
- sedRegexpLen=${#sedRegexp}-1
- sedRegexp=${sedRegexp:0:sedRegexpLen}
- echo $sedRegexp
- echo "_______"
- # Ask user if changes look good, if not, restart script
- echo "Display results? "
- getYesNo
- displayResults=$?
- if [[ $displayResults == 0 ]]; then
- for oldfile in *
- do
- newname=$(echo $oldfile | sed $sedRegexp)
- echo "old: "$oldfile" | new: "$newname
- done
- fi
- echo "_______"
- # Ask user if changes look good, if not, restart script
- echo "Continue with renaming? If NO, script with restart. "
- getYesNo
- response=$?
- if [[ $response == 0 ]]; then
- bash batch_renamer.sh
- fi
- echo "_______"
- # Rename files
- for oldfile in *
- do
- newname=$(echo $oldfile | sed $sedRegexp)
- # If new and old name are the same, skip
- if [[ $oldfile == $newname ]]; then
- continue
- fi
- # Echo files being changed and change them
- if [[ $displayResults == 0 ]]; then
- echo $oldfile $newname
- fi
- mv $oldfile $newname
- done
- # Return file separator to default
- IFS=$oldIFS