4

I am trying to write a patch file that will do the following:

  • Search w:\ for all *.pdf and *.xls files. There are many directories and sub-directories to search.
  • I need to search the file name and replace the following. Not every file name will have these undesirable characters in them.
    • Find & and replace with and
    • Find # and remove the #
    • Find “ LTD.” (There is a space before the “L”) and remove it.
  • Rename the file if any of the above changes have been made.
  • Output a file of all files changed.

I am having some problems hence my post here. I have been able to piece this code together from other posts on this site. This is what I have so far. Go easy on me it has been 18 years since I did any programing in college.

I am using a program called Take Command by JP Software so I could use some sort of debugger and try to figure out what I was doing wrong.

Thank you for any and all help.

@Echo OFF
set /A xcount=0
set root=c:\clients
Set logfilename=Changed.txt
cd %root%

for /f "delims=" %%a in ('dir /a:-d /o:n /b /s *.pdf *.xls') do call :next "%%a"
echo. >> %root%\%logfilename%
echo End of file > %root%\%logfilename%
pause
GOTO:EOF

:next
    set "current=%~nx1"
    set "newname=%~nx1"
    set "newname=%newname:&=and%"
    set "newname=%newname:#=%"
    set "newname=%newname: LTD.=%"
    set "newname=%newname:%=%"
    set "newname=%newname::=%"

If "%current%" == "%newname%" goto nope
    SET /A xcount+=1
    ren "%current%" "%newname%"
    echo "%current%" "%newname%"
    echo %xcount% - "%current%" "%newname%" > %root%\%logfilename%
:nope
CCurrell
  • 41
  • 2
  • Batch files, by comparison, are hard to read, hard to write, hard to debug, and not very powerful. Perhaps you'd have an easier time trying to do this in another language, maybe C# or VisualBasic – salezica Aug 25 '14 at 20:01
  • I don't disagree with you at all. Back in college I was pretty good at C++ but that was a long time ago. I doubt I could do a "Hello World" program in C++ today. I have done a little visual basic as well but years ago. I am networking guy not a programmer. So I really just need a something to write quick little programs like this. This little batch file when it works will save one of employees days worth of work. – CCurrell Aug 25 '14 at 20:17
  • just a little annotation in addition to dbenhams answer: if you want to call a subroutine, you have to end this routine with `goto :eof` to return. – ths Aug 25 '14 at 21:08
  • @speising - or EXIT /B, or neither if the routine is at the end of the file. But it is better to always have EXIT /B or GOTO :EOF just in case another routine is later added. – dbenham Aug 26 '14 at 20:27

2 Answers2

1

I see 2 major problems:

1) Your REN command must have the full path to the source file. You are providing the name and extension only.

2) It looks like you are attempting to strip out % characters as well. That cannot be done with normal expansion - delayed expansion is required. Also, the % must be doubled.

I see a number of inefficiencies:

1) There is no need for any CALL or GOTO. Both are relatively slow. Everything can be done within one loop using parentheses. The performance difference could be significant if there are many files to process. Note that working with variables that have been set within a block requires delayed expansion.

2) Repeatedly opening and closing your log file to append more information is slow. It is more efficient to enclose an entire block within parentheses and redirect only once.

3) You are attempting to remove : from the name. This is not necessary because Windows file names cannot contain :.

4) You have already set your current directory to the root, so there is no need to include the path when redirecting.

Here are a few points of note for my untested solution below:

1) Delayed expansion is toggled on and off within the loop to protect any ! that may appear within file names or paths. FOR variables are corrupted upon expansion if they contain ! and delayed expansion is enabled.

2) I used echo( instead of echo. to echo a blank line. This syntax looks weird, but it always works. The echo. synyax can fail under certain obscure conditions.

3) I used PUSHD insead of CD to set the current directory to your root. CD will not work properly without the /D option if you are changing the current volume (D: to C: for example)

4) The outer block redirects a non-standard/unused file handle to the logfile. Then within the block, specific outputs are redirected to the logfile handle. This is done so that the logfile does not have to be repeatedly opened, repositioned to end, and closed. Other outputs go to normal stdout.

@echo off
setlocal disableDelayedExpansion
set /a xcount=0
set "root=c:\clients"
Set "logfile=Changed.txt"
pushd "%root%"

3>"%logfile%" (
  for /f "delims=" %%F in ('dir /a:-d /o:n /b /s *.pdf *.xls') do (
    set "file=%%~fF"
    set "old=%%~nxF"
    set "new=%%~nxF"
    setlocal enableDelayedExpansion
    set "new=!new:&=and!"
    set "new=!new:#=!"
    set "new=!new: LTD.=!"
    set "new=!new:%%=!"
    if "!new!" neq "!old!" (
      set /a xcount+=1
      ren "!file!" "!new!"
      echo "!file!" "!new!"
      echo !xcount! - "!file!" "!new!">&3
    )
    endlocal
  )
  echo(>&3
  echo End of file>&3
)
popd
pause
exit /b

Note - The solution above implements the rules as you have stated them. But there is a problem. A name like "Acme LTD.pdf" would be renamed "Acmepdf"


UPDATE

Since answering, I've written a handy utility called JREN.BAT that can solve the problem very simply. The utility performs rename operations by doing regular expression search and replace on file names. JREN.BAT is a hybrid JScript/batch script that runs natively on any Windows machine from XP onward.

I've refined your rules a bit so that LTD occurring immediately before the extension preserves the dot for the extension, and LTD. occurring in the middle of the name removes the dot.

Assuming you have JREN.BAT in your current directory, or better yet, somewhere within your PATH, then the following script should do the trick.

renFiles.bat

@echo off
call jren "(&)|(#|%| LTD(?=\.[^.]*$)| LTD\.)" "$1?'and':''" /i /s /j /fm "*.pdf|*.xls" %1 >rename.log
type rename.log

I recommend running the above in test mode using the /T option. It will simply list the proposed rename operations without actually renaming anything.

renFiles /T

After verifying everything looks good, then run again without /T

dbenham
  • 127,446
  • 28
  • 251
  • 390
  • Why do you need the file variable isn't ren "%%F" enough? – Ir Relevant Aug 25 '14 at 21:14
  • @IrRelevant - `%%F` will be corrupted if it contains `!` because delayed expansion is enabled. The value of `file` must be set before delayed expansion is enabled. – dbenham Aug 25 '14 at 21:18
  • Thank you dbenham. I have to agree with one the comments above, - Batch files, by comparison, are hard to read, hard to write, hard to debug. As I try to understand your code I copied and pasted in into the debugger I am using. I get some odd values in the variable file. The extension is always wrong for any file assigned to the variable file. If the file name is path\abc.pdb the variable file shows is as path\abc.pdfile. This results in an error in the ren command. Not sure how to correct this? – CCurrell Aug 26 '14 at 19:04
  • @CCurrell - I've never used Take Command, or any other batch debugger, so I can't speculate on what the problem is. But I have now tested the code, and it works properly for me when executed as a batch script within a CMD.EXE console session. I did edit the code to fix some logging issues, but I can't see how my code could have given the result you describe. Try putting ECHO in front of the REN command, and execute directly as a normal batch script. It should work, with REN commands displayed to the screen but not executed. Remove the ECHO when everything is confirmed to work properly. – dbenham Aug 26 '14 at 21:40
  • @CCurrell - I've discovered a potential problem with your rename rules, and I've added a much simpler solution. – dbenham Dec 13 '14 at 15:10
0

Given a directory u:\sourcedir\t w o

 Volume in drive U has no label.
 Volume Serial Number is 0460-0000

 Directory of u:\sourcedir\t w o

14/12/2014  00:05    <DIR>          .
14/12/2014  00:05    <DIR>          ..
14/12/2014  00:28                 0 dum myfile2.txt
14/12/2014  00:28                 0 file1.xls
14/12/2014  00:28                 0 file2.xls
14/12/2014  00:28                 0 file3.xls
14/12/2014  00:28                 0 amper&1.pdf
14/12/2014  00:28                 0 amper&2.pdf
14/12/2014  00:28                 0 amper&3.pdf
14/12/2014  00:28                 0 hash#1.xls
14/12/2014  00:28                 0 hash#2.xls
14/12/2014  00:28                 0 hash#3.xls
14/12/2014  00:28                 0 LTD LTD 1.pdf
14/12/2014  00:28                 0 LTD LTD 2.pdf
14/12/2014  00:28                 0 LTD LTD 3.pdf
14/12/2014  00:28                 0 lower ltd 1.pdf
14/12/2014  00:28                 0 lower ltd 2.pdf
14/12/2014  00:28                 0 lower ltd 3.pdf
14/12/2014  00:28                 0 ALL#& LTD 1.pdf
14/12/2014  00:28                 0 ALL#& LTD 2.pdf
14/12/2014  00:28                 0 ALL#& LTD 3.pdf
14/12/2014  00:28                 0 shriek!hello!1.pdf
14/12/2014  00:28                 0 shriek!hello!2.pdf
14/12/2014  00:28                 0 shriek!hello!3.pdf
14/12/2014  00:28                 0 pcnt%hello%1.pdf
14/12/2014  00:28                 0 pcnt%hello%2.pdf
14/12/2014  00:28                 0 pcnt%hello%3.pdf
14/12/2014  00:28                 0 co,mm,a1.pdf
14/12/2014  00:28                 0 co,mm,a2.pdf
14/12/2014  00:28                 0 co,mm,a3.pdf
14/12/2014  00:28                 0 se;mi1.pdf
14/12/2014  00:28                 0 se;mi2.pdf
14/12/2014  00:28                 0 se;mi3.pdf
14/12/2014  00:28             1,074 newfile.txt
              32 File(s)          1,074 bytes
               2 Dir(s)   2,099,904,512 bytes free

Then this batch:

@ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir\t w o"
PUSHD "%sourcedir%"
(
FOR /f "delims=" %%a IN (
 'dir /b /a-d *.pdf *.xls'
 ) DO (
SET "oname=%%a"
CALL :alter
)
)>newfile.txt
popd
GOTO :EOF

:alter
SET "name=%oname:&=and%"
SET "name=%name:#=%"
SET "name=%name: LTD=%"
ECHO(REN "%oname%" "%name%"
GOTO :eof

Yields, in u:\sourcedir\t w o\newfile.txt

REN "amper&1.pdf" "amperand1.pdf"
REN "amper&2.pdf" "amperand2.pdf"
REN "amper&3.pdf" "amperand3.pdf"
REN "LTD LTD 1.pdf" "LTD 1.pdf"
REN "LTD LTD 2.pdf" "LTD 2.pdf"
REN "LTD LTD 3.pdf" "LTD 3.pdf"
REN "lower ltd 1.pdf" "lower 1.pdf"
REN "lower ltd 2.pdf" "lower 2.pdf"
REN "lower ltd 3.pdf" "lower 3.pdf"
REN "ALL#& LTD 1.pdf" "ALLand 1.pdf"
REN "ALL#& LTD 2.pdf" "ALLand 2.pdf"
REN "ALL#& LTD 3.pdf" "ALLand 3.pdf"
REN "shriek!hello!1.pdf" "shriek!hello!1.pdf"
REN "shriek!hello!2.pdf" "shriek!hello!2.pdf"
REN "shriek!hello!3.pdf" "shriek!hello!3.pdf"
REN "pcnt%hello%1.pdf" "pcnt%hello%1.pdf"
REN "pcnt%hello%2.pdf" "pcnt%hello%2.pdf"
REN "pcnt%hello%3.pdf" "pcnt%hello%3.pdf"
REN "co,mm,a1.pdf" "co,mm,a1.pdf"
REN "co,mm,a2.pdf" "co,mm,a2.pdf"
REN "co,mm,a3.pdf" "co,mm,a3.pdf"
REN "se;mi1.pdf" "se;mi1.pdf"
REN "se;mi2.pdf" "se;mi2.pdf"
REN "se;mi3.pdf" "se;mi3.pdf"
REN "file1.xls" "file1.xls"
REN "file2.xls" "file2.xls"
REN "file3.xls" "file3.xls"
REN "hash#1.xls" "hash1.xls"
REN "hash#2.xls" "hash2.xls"
REN "hash#3.xls" "hash3.xls"
Magoo
  • 77,302
  • 8
  • 62
  • 84