I am trying to write a bash shell script (which is called by an Automator Action) to rename TV show DVD rips that I have named badly over the years. I want to remove part of the text in the filenames. I want to remove the text that appears after a specific series of characters that I know will always appear in the filename. But I do not know how many characters will appear before or after the known series of characters. I also don't know if the before or after text will contain multiple periods or dashes. An example would probably help:


I know that each file will always contain a string in the format of SxxExx where the x's are always numbers. But I do not know what the numbers will be. I want to get the filename up to and including the SxxExx string and the file extension but strip out everything else. So for the above example I would end-up with:


I have tried using bash's built-in string replacement commands. I thought the expr index command would give me the integer start point of the SxxExx string and then I could use ${filename:offset:length} to extract only the required part of the filename (i already know the extension so that can be re-added). But it seems the OS X version of expr doesn't include the index functionality. I have only scripted in Basic and LotusScript before. In those environments this would have been fairly easy using commands such as 'Like' and 'Instr' or 'Mid'. But in bash I just can't figure it out. I have spent hours googling trying to understand how to use regular expressions to locate the 'SxxExx' substring in the filename but I just can't figure it out. I hope some clever UNIX scripters will be able to help me!

ls | perl -nl -e '/(.*)(S[0-9]+E[0-9]+).*(\.mp4)/ && print "mv \"" . $_ . "\" \"". $1 . $2 . $3 . "\""'

How does this work? First ls outputs the list of files, one per line, like so:


Then perl -nl splits this into lines, feeding each to the regex, much like awk*. The regex captures 3 groups (denoted by parentheses), first the bit before SxxEyy, then that, then the file suffix. It then simply assembles a mv command suitable for renaming the files, like so:

mv "The.Big.Bang.Theory.S01E01.xxxxxxxx.mp4" "The.Big.Bang.Theory.S01E01.mp4"
mv "The.Big.Bang.Theory.S01E02.somecrap.mp4" "The.Big.Bang.Theory.S01E02.mp4"
mv "The.Big.Bang.Theory.S04E12.otherjunk.mp4" "The.Big.Bang.Theory.S04E12.mp4"

This can then be inspected and once you're satisfied it does what you want, piped into a shell by appending: | sh.

*awk would normally be a good tool to use for this, but sadly only GNU awk supports regex capture groups and Mac OS X doesn't include gawk by default.

| improve this answer | |
  • Thanks so much for your response. Rather than rename a series of files in a folder, I just want the script to process the file that is being added to a folder (the bash script is called by a Folder Action). I actually want to get the filename (stripped of the rubbish after the SxxExx), rename the single file and then pass that filename to an app (iDentify) that will look-up the TV show tag info from TVDB.com. How would I use your code to do that? – Stu Aug 22 '11 at 11:31
  • Just edit the print statement as needed, the syntax should be pretty self explanatory. Then call it to return the filtered name, like so: CLEANED=$(echo "${FNAME}" | perl -nl -e '/(.*)(S[0-9]+E[0-9]+).*(\.mp4)/ && print $1 . $2 . $3') (assuming FNAME contains the raw filename). – Ingmar Hupp Aug 22 '11 at 11:46
  • Fantastic. This works perfectly. Thank you so much for your help. Much appreciated. – Stu Aug 22 '11 at 12:33
  • One final question. How would I make this non-case sensitive, so it would match S01E01 or s1e01 in the filename? And if I have the target file's extension in a variable how do i specify the variable. The following doesn't work: clean=$(echo "${cfile}" | perl -nl -e '/(.*)(S[0-9]+E[0-9]+).*(\.$outext)/ && print $1 . $2 . $3') where $outext is a variable that contains the file's extension. – Stu Aug 22 '11 at 12:58
  • Don't parse the output of ls. You could use perl -e 'foreach (@ARGV) {…}' *. Using Perl isn't necessary though, see glenn jackman's answer. – Gilles 'SO- stop being evil' Aug 22 '11 at 13:12

With bash only:

for filename in *; do
    if [[ "$filename" =~ (.*S[0-9][0-9]E[0-9][0-9]).*(\....)$ ]]; then
        echo mv "$filename" "${BASH_REMATCH[1]}${BASH_REMATCH[2]}"

Remove the "echo" when you're satisfied it's working.

| improve this answer | |
  • Nice, didn't know bash can do captured groups! – Ingmar Hupp Aug 22 '11 at 12:37
  • Thanks Glenn. I don't really understand your response or how the BASH_REMATCH pieces work :-) but thank you for taking the time to respond. Much appreciated! – Stu Aug 22 '11 at 13:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .