2

In a windows batch file, I would like to rename files containing a 4-digit year (ex: "1999") in the filename by simply wrapping the year string in parentheses. Example:

home video 1998.avi
home vid 1987.mov
home_video (2002).avi

would become

home video (1998).avi
home vid (1987).mov
home_video (2002).avi

Notice that if it's already wrapped in parentheses, I'd prefer not to double them up.

So far, I have only been able to match the file names containing a year string with the following code:

@echo off
REM Match file names with 4-digit year
setlocal enableDelayedExpansion

for /f "tokens=1* delims=" %%A in (
  'dir /B "*"^|findstr "[1-2][0-9][0-9][0-9]" '
) do @echo %%A

pause
REM Now what?

So I can output a list of matching file names, but from there I do not know how to target the grouped characters that findstr matched in order to parse the full file name into the 3 chunks I believe I would need: the substring preceding the matched group, the group itself, and the substring following the group.

Is this possible in a batch file?

anthony
  • 274
  • 1
  • 2
  • 12
  • 1
    Does the 4-digit year comes only at end of file name like in `home video 1998.avi` or could it be also in the middle of the file name like in `home video 1998 part2.avi` ? – sst Sep 01 '18 at 07:07
  • I would also propose that you use use brackets instead of parentheses in file or directory names, _Microsoft were foolish in using `C:\Program Files (x86)`_. – Compo Sep 01 '18 at 12:15
  • @sst, could be any position in the file name. the find-replace directives in Mofi's answer take care of this. – anthony Sep 02 '18 at 05:15
  • @Compo, that's a fair suggestion. I think with a couple of tweaks to lines #13 and #19 of Mofi's code, that would be an easy edit! – anthony Sep 02 '18 at 05:17

1 Answers1

1

I use since more than 20 years Total Commander (shareware) for file/folder renaming tasks which makes it possible with its built-in multi-rename tool to easily rename files and folders with just a few clicks on which the results can be viewed before really running the multi-rename and which even supports undo after having done the multi-rename. Well, in real I use Total Commander for nearly all file management tasks.

But it was interesting to develop the code for this very special file renaming task with all the limitations Windows command processor has because of not being designed for such tasks.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "eol=| delims=" %%I in ('dir /A-D-H /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /R /C:"19[89][0123456789]" /C:"20[012][0123456789]"') do call :RenameFile "%%I"
endlocal
goto :EOF

:RenameFile
set "FileName=%~n1"
setlocal EnableDelayedExpansion
set "Year=1980"

:YearLoop
set "NewName=!FileName:%Year%=(%Year%)!"
if "!NewName!" == "!FileName!" (
    if %Year% == 2029 goto ExitSub
    set /A Year+=1
    goto YearLoop
)
if "!FileName:(%Year%)=!" == "!FileName!" ren "%~1" "!NewName!%~x1"

:ExitSub
endlocal
goto :EOF

FOR executes the following command line in a separate command process started in background with cmd.exe /C:

dir /A-D-H /B 2>nul | C:\Windows\System32\findstr.exe /R /C:"19[89][0123456789]" /C:"20[012][0123456789]"

DIR outputs with the used options all names of non-hidden files in current directory with just file name + extension and without file path. An error message output in case of current directory does not contain any non-hidden file is suppressed by redirecting it from handle STDERR to device NUL with 2>nul.

The file names output by DIR are redirected with | to handle STDIN of command FINDSTR which searches case-sensitive with two regular expression interpreted search strings for four digits in range 1980 to 1999 or in range 2000 to 2029. There is no check made if a match of a four digit number is part of a larger number like 12000 or 19975. And there is no check made if there are already round brackets around the four digit number.

FINDSTR interprets also ¹, ², ³ as digit on using [0-9] which is the reason for using [0123456789] to really match only any of those 10 digit characters. Please read for more details about FINDSTR the articles SS64 - FINDSTR and What are the undocumented features and limitations of the Windows FINDSTR command?

FINDSTR outputs all file names containing four digits in range 1980 to 2029 to handle STDOUT of background command process.

Please read the Microsoft article about Using Command Redirection Operators for an explanation of 2>nul and |. The redirection operators > and | must be escaped with caret character ^ on FOR command line to be interpreted as literal characters when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line in a separate command process started in background.

FOR captures those lines and processes them line by line. The default for option eol= (end of line) is a semicolon and so FOR would ignore all file names starting with a semicolon. For that reason eol=| is specified because a vertical bar cannot be used in a file name and so all captured file names are processed by FOR.

FOR would split up each file name on spaces/tabs by default and assigns only the first substring (token) to specified loop variable I. This splitting behavior is disabled by using delims= which defines an empty list of delimiters. tokens=* is not the same as this results in removing leading spaces from the file names. File names can start with one or more spaces although this is very unusual.

A file name can contain also exclamation marks ! which must be also taken into account on using delayed environment variable expansion. Each file name is passed to a subroutine for further processing it.

A loop is used to replace all occurrences of year assigned to loop variable Year by the year in round brackets until new file name is different to current file name because the substitution was indeed positive for searched string. for /L %%J in (1980,1,2029) do ... was not used as this loop can't be exited once having found the right year in file name.

After having found the year in file name it is checked if this year is not already embedded in parentheses to avoid renaming a file with name home vid (1987).mov to home vid ((1987)).mov. So for example home video 1998.avi is renamed finally to home video (1998).avi.

A file name containing two numbers with four or more digits is also not processed correct as this code can't find out what is the year in such a file name.

This batch code is not really fast, but it should work with the listed limitations.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • call /?
  • dir /?
  • echo /?
  • endlocal /?
  • findstr /?
  • goto /?
  • if /?
  • ren /?
  • set /?
  • setlocal /?

See also Where does GOTO :EOF return to?

PS: File names with ( or ) in name make processing them with a batch file very often more difficult as in this case the file name must be always enclosed in double quotes like for file names containing a space character because of ( and ) have also a special meaning for Windows command processor cmd.exe as it can be seen on code above. See also How does the Windows Command Interpreter (CMD.EXE) parse scripts?

Mofi
  • 38,783
  • 14
  • 62
  • 115
  • This worked perfectly! And holy cow this was a thorough answer and explanation, sir. – anthony Sep 02 '18 at 05:04
  • Thank you very much! I learned a lot just from tinkering with the code, and even more from reading through your step by step commentary. Answers like this are to be commended! I will note that, for my purposes of renaming a few hundred files, this solution is great, but if you are batching thousands of files or more, I do believe this might a bit slow, as Mofi mentioned. – anthony Sep 02 '18 at 05:09