How to rename batches of files using .NET Regular Expressions

by Klaus Graefensteiner 9. June 2008 06:16

Introduction

The tool that I am presenting here renames batches of files that have some kind of numerical index as part of their file name. It can rename the text before the index, it can shift the index numbers, give the files a new extension and add or remove leading zeros to and from the numerical index part of the file name. It uses Regular Extensions to parse the file names.

Thumbnail images of the scanned pages of my 1988 year book

Figure 1: Thumbnail images of the scanned pages of my 1988 year book

The War Story

I was trying the other day to scan in an old high school year book. I used a Canon CanoScan LiDE 600F scanner (0302B002). The Toolbox software that came with the scanner indexes the file names automatically. If you call your project e.g. “Abizeitung” then the name of the file of the first scan is called “Abizeitung_0001.jpg”, the name of the 10th scan is called “Abizeitung_0010.jpg” and so on. My goal was to have the index in the file name match the page number of the scanned page. After scanning 38 pages I made a mistake. I scanned the sheet with page 39 and page 40 twice. At this point the file index and the pages got out of sync. Page 41 was now “Abizeitung_0043.jpg”. The index of the files of page 41 and higher was shifted by 2. Just later when I was about to scan pages 120 and 121 I noticed that I screwed up the file numbering and deleted the duplicate scans. I deleted “Abizeitung_0039.jpg” and “Abizeitung_0040.jpg” and continued scanning. This time the CanoScan Toolbox software outsmarted me again, by using the now two empty file name slots to store the scans of pages 120 and 121. The following table shows the full extent of the scan screw up:

File Name Index Page Number Comment
1 1  
2 2  
... ...  
37 37  
38 38  
39 37, 120

First I scanned page 37 twice, which created file index 39, then later when I discovered that page number and file index are out of synch I deleted the file with index 39. But the scanning tool used the empty file index slot and filled it up with the scan of page number 120.

40 38, 121

First I scanned page 38 twice, which created file index 40, then later when I discovered that page number and file index are out of synch I deleted the file with index 40. But the scanning tool used the empty file index slot and filled it up with the scan of page number 121.

41 39  
42 40  
... ...  
119 117  
120 118  
121 119

The scan of page 120 filled up file index slot 39 and the scan of pate 121 filled up file index slot 40.

122 122

Now the page number and the file index are in synch again.

... ...  
136 136  

PowerShell is not EasyShell

I thought "no problem, just write a quick PowerShell script". Fairly quickly I found out that PowerShell is not EasyShell and actually requires a little bit of learning. I decided to first start reading Bruce Payette's book Windows PowerShell in Action and revisit this idea once I am finised with the book.

C Sharp is EasyDev

For the quick fix I decided to create little C# WinForms application using regular expressions and a SortedDictionary<int, FileInfo> to straighten out the stray file names.

Source code

The following paragraph shows the source code file that does the actual file name parsing and renaming work.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.ComponentModel;
   4: using System.Data;
   5: using System.Drawing;
   6: using System.Text;
   7: using System.Windows.Forms;
   8: using System.Text.RegularExpressions;
   9: using System.IO;
  10:  
  11: namespace RenameFiles
  12: {
  13:     public partial class Form1 : Form
  14:     {
  15:         public Form1()
  16:         {
  17:             InitializeComponent();
  18:             button2.Enabled = false;
  19:         }
  20:  
  21:         private Regex FileNameRegex;
  22:         private void button1_Click(object sender, EventArgs e)
  23:         {
  24:             openFileDialog1.ShowDialog();
  25:         }
  26:  
  27:  
  28:         private FileInfo FirstFile;
  29:         private Match FileNameMatch;
  30:         private void openFileDialog1_FileOk(object sender, CancelEventArgs e)
  31:         {
  32:             if (!e.Cancel)
  33:             {
  34:                 OpenFileDialog FirstFileDlg = sender as OpenFileDialog;
  35:                 FirstFile = new FileInfo(FirstFileDlg.FileName);
  36:                 FileNameRegex = new Regex(textBox1.Text, RegexOptions.Compiled | RegexOptions.IgnoreCase);
  37:                 FileNameMatch = FileNameRegex.Match(FirstFile.Name);
  38:                 if (FileNameMatch.Success)
  39:                 {
  40:                     textBox2.Text = FileNameMatch.Groups["NAME"].Value;
  41:                     numericUpDown1.Value = Convert.ToInt32(FileNameMatch.Groups["INDEX"].Value);
  42:                     textBox3.Text = FileNameMatch.Groups["EXT"].Value;
  43:  
  44:  
  45:                     ChangeLabel();
  46:  
  47:                     button2.Enabled = true;
  48:                     numericUpDown2.Enabled = true;
  49:                     numericUpDown1.Enabled = true;
  50:                     numericUpDown3.Enabled = true;
  51:                     numericUpDown3.Value = numericUpDown1.Value;
  52:                     checkBox1.Enabled = true;
  53:                     textBox2.Enabled = true;
  54:                     textBox3.Enabled = true;
  55:                 }
  56:                 else
  57:                 {
  58:                     MessageBox.Show("Selected File doesn't match Regular Expression!");
  59:                 }
  60:             }
  61:         }
  62:  
  63:         private void button2_Click(object sender, EventArgs e)
  64:         {
  65:             FileNameRegex = new Regex(textBox1.Text, RegexOptions.Compiled | RegexOptions.IgnoreCase);
  66:             DirectoryInfo Folder = FirstFile.Directory;
  67:  
  68:             Match m;
  69:             SortedDictionary<int, FileInfo> MatchedFiles = new SortedDictionary<int, FileInfo>();
  70:             
  71:             int Index = 0;
  72:             int MinIndex = (int)numericUpDown1.Value + 9999;
  73:             int MaxIndex = 0;
  74:             int ShiftIndexBy = (int)numericUpDown2.Value;
  75:  
  76:             foreach (FileInfo f in Folder.GetFiles())
  77:             {
  78:                 
  79:                 //Match file names
  80:                 m = FileNameRegex.Match(f.Name);
  81:  
  82:                 if (m.Success)
  83:                 {
  84:                     Index = Convert.ToInt32(m.Groups["INDEX"].Value);
  85:  
  86:                     if (Index >= numericUpDown1.Value && Index <= numericUpDown3.Value)
  87:                     {
  88:                         try
  89:                         {
  90:                             MatchedFiles.Add(Index, f);
  91:                             if (Index <= MinIndex) MinIndex = Index;
  92:                             if (Index >= MaxIndex) MaxIndex = Index;
  93:                         }
  94:                         catch (Exception Ex)
  95:                         {
  96:                             MessageBox.Show(Ex.Message + "\n" + "Another match for " 
  97:                                 + f.Name + " already exists in file list!", "Error while filtering files", 
  98:                                 MessageBoxButtons.OK, MessageBoxIcon.Error);
  99:                         }
 100:                     }
 101:                 }
 102:  
 103:             }
 104:  
 105:             MessageBox.Show(MatchedFiles.Count.ToString() + " files found\n" +
 106:                             "First file index " + MinIndex.ToString() + "\n" +
 107:                             "Last file index " + MaxIndex.ToString(), "Renaming files...", 
 108:                             MessageBoxButtons.OK, MessageBoxIcon.Information);
 109:  
 110:             if((MatchedFiles.Count == 0) || ( ShiftIndexBy == 0 
 111:                 && textBox2.Text == FileNameMatch.Groups["NAME"].Value 
 112:                 && checkBox1.Checked == true 
 113:                 && textBox3.Text == FileNameMatch.Groups["EXT"].Value ))
 114:             {
 115:                 MessageBox.Show("There is no file to be renamed. DONE!");
 116:             }
 117:  
 118:             if (MatchedFiles.Count > 0 && ShiftIndexBy <= 0)
 119:             {
 120:                 FileInfo fi;
 121:                 for (int i = MinIndex; i <= MaxIndex; i++ )
 122:                 {
 123:                     if (MatchedFiles.TryGetValue(i, out fi))
 124:                     {
 125:                         string NewFileName = textBox2.Text 
 126:                             + (checkBox1.Checked ? String.Format("{0:0000}", i 
 127:                             + numericUpDown2.Value) : String.Format("{0}", i 
 128:                             + numericUpDown2.Value)) + textBox3.Text;
 129:                         try
 130:                         {
 131:                             fi.MoveTo(NewFileName);
 132:                         }
 133:                         catch (Exception Ex)
 134:                         {
 135:                             MessageBox.Show(Ex.Message + "\n" + "Attempt to rename " 
 136:                                 + fi.Name + " to " + NewFileName + " failed!", 
 137:                                 "Error while moving files", MessageBoxButtons.OK, MessageBoxIcon.Error);
 138:                         }
 139:                     }
 140:                 }
 141:             }
 142:  
 143:  
 144:             
 145:             if (MatchedFiles.Count > 0 && ShiftIndexBy > 0)
 146:             {
 147:                 FileInfo fi;
 148:                 for (int i = MaxIndex; i >= MinIndex; i--)
 149:                 {
 150:                     if (MatchedFiles.TryGetValue(i, out fi))
 151:                     {
 152:                         string NewFileName = textBox2.Text 
 153:                             + (checkBox1.Checked ? String.Format("{0:0000}", i 
 154:                             + numericUpDown2.Value) : String.Format("{0}", i 
 155:                             + numericUpDown2.Value)) + textBox3.Text;
 156:                         try
 157:                         {
 158:                             fi.MoveTo(NewFileName);
 159:                         }
 160:                         catch (Exception Ex)
 161:                         {
 162:                             MessageBox.Show(Ex.Message + "\n" + "Attempt to rename " 
 163:                                 + fi.Name + " to " + NewFileName + " failed!", 
 164:                                 "Error while moving files", MessageBoxButtons.OK, 
 165:                                 MessageBoxIcon.Error);
 166:                         }
 167:                     }
 168:                 }
 169:             }                      
 170:         }
 171:  
 172:         private void numericUpDown1_ValueChanged(object sender, EventArgs e)
 173:         {
 174:             ChangeLabel();
 175:         }
 176:  
 177:         private void ChangeLabel()
 178:         {
 179:             label1.Text = "Renames all files in a folder from e.g. \"" 
 180:                 + FileNameMatch.Groups["NAME"].Value 
 181:                 + String.Format("{0:0000}", (int)numericUpDown1.Value) 
 182:                 + FileNameMatch.Groups["EXT"].Value + "\" to \"" 
 183:                 + (checkBox1.Checked ? textBox2.Text 
 184:                 + String.Format("{0:0000}", (Convert.ToInt32(FileNameMatch.Groups["INDEX"].Value) 
 185:                 + numericUpDown2.Value)) : textBox2.Text 
 186:                 + String.Format("{0}", (Convert.ToInt32(FileNameMatch.Groups["INDEX"].Value) 
 187:                 + numericUpDown2.Value))) + textBox3.Text +"\"";
 188:         }
 189:  
 190:         private void numericUpDown2_ValueChanged(object sender, EventArgs e)
 191:         {
 192:             ChangeLabel();
 193:         }
 194:  
 195:         private void textBox2_TextChanged(object sender, EventArgs e)
 196:         {
 197:             ChangeLabel();
 198:         }
 199:  
 200:         private void checkBox1_CheckedChanged(object sender, EventArgs e)
 201:         {
 202:             ChangeLabel();
 203:         }
 204:  
 205:         private void textBox3_TextChanged(object sender, EventArgs e)
 206:         {
 207:             ChangeLabel();
 208:         }
 209:  
 210:         private void Form1_Load(object sender, EventArgs e)
 211:         {
 212:  
 213:         }
 214:  
 215:         private void numericUpDown3_ValueChanged(object sender, EventArgs e)
 216:         {
 217:             ChangeLabel();
 218:         }
 219:  
 220:  
 221:     }
 222: }

Download

The complete Visual Studio 2005 Solution can be downloaded here: RenameFiles_1_0.zip

Batch Rename Files in Action

The movie

The following movie shows the utility in action:

The step-by-step instructions

Step 1

First rename the files with the index 39 and 40 with new place holder name to prepare the next step.

Batch Rename Files First Step

Figure 2: Batch Rename Files Step 1

Step 2

Now rename files by decrementing the index by 2 starting with file index 41 and ending with file index 121.

Batch Rename Files Second Step

Figure 3: Batch Rename Files Step 2

Step 3

Now we need to move the two files from step 1 to the final destination.

Batch Rename Files Third Step

Figure 4: Batch Rename Files Step 3

Ausblick

Initially I just wanted to scan a year book and share it with my former class mates as PDF file, but I ended up creating a C# application for renaming numbered files. Onother classical case of "Vom Hundertsten Ins Tausendste!"

Tags: , , , , , ,

.NET Framework | Photoshop | PowerShell

Comments are closed

About Klaus Graefensteiner

I like the programming of machines.

Add to Google Reader or Homepage

LinkedIn FacebookTwitter View Klaus Graefensteiner's profile on Technorati
Klaus Graefensteiner

Klaus Graefensteiner
works as Developer In Test and is founder of the PowerShell Unit Testing Framework PSUnit. More...

Open Source Projects

PSUnit is a Unit Testing framwork for PowerShell. It is designed for simplicity and hosted by Codeplex.
BlogShell is The tool for lazy developers who like to automate the composition of blog content during the writing of a blog post. It is hosted by CodePlex.

Administration

About

Powered by:
BlogEngine.Net
Version: 1.6.1.0

License:
Creative Commons License

Copyright:
© Copyright 2014, Klaus Graefensteiner.

Disclaimer:
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Theme design:
This blog theme was designed and is copyrighted 2014 by Klaus Graefensteiner

Rendertime:
Page rendered at 4/21/2014 7:39:51 AM (PST Pacific Standard Time UTC DST -7)