Take the 2-minute tour ×
Ask Different is a question and answer site for power users of Apple hardware and software. It's 100% free, no registration required.

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:

The.Big.Bang.Theory.S01E01.xxxxxxxx.mp4

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:

The.Big.Bang.Theory.S01E01.mp4

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!

share|improve this question
    
Posting this on unix.stackexchange.com may have a faster response. –  Nivas Aug 22 '11 at 10:43
    
Thanks. I have posted on unix.stackexchange.com too. –  Stu Aug 22 '11 at 11:17

2 Answers 2

up vote 1 down vote accepted
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:

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

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.

share|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 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]}"
    fi
done

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

share|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

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.