Check batch file is called at most N times (for given arguments)

Microsoft Windows
Post Reply
User avatar
MigrationUser
Posts: 336
Joined: 2021-Jul-12, 1:37 pm
Contact:

Check batch file is called at most N times (for given arguments)

Post by MigrationUser »

05 Jun 2014 08:18
bucsie

I have a batch file that receives as one of its arguments a file name, and performs some tasks on it.
This task can fail, so in case of failure, I need to retry again, later.

My main purpose is to execute the batch at most 5 times for a particular file.
In order to do this, I redirected its outputs (and echoed the calling arguments) to a log.file.

I want to check if the same command has been issued before, so I do a find:

Code: Select all

SETLOCAL
setlocal EnableDelayedExpansion
    @echo %FileName% >>"d:\temp\tmp.file"
    for /f "tokens=3" %%k in ('find /C ^"%FileName%^" ^"d:\temp\tmp.file^"') do set retryC=%%k
    @echo retry count = %retryC%
    @echo local retry count = !retryC!
----------------------------

#2 05 Jun 2014 08:22
bucsie

Sorry, I accidentally pressed enter and didn't finish the question. So, continuing:

This will only echo a zero result in the log file no matter how many times it's executed.

Code: Select all

retry count = 0
local retry count = 0
However, if I copy the line that's being output in the log by the for command, and execute it directly in the console, it works:

Code: Select all

>for /F "tokens=3" %k in ('find /C "file.ex1.zip.blabla_xx" "d:\temp\tmp.file"') do set retryC=%k 
>set retryC=28
Any ideas why this happens and how I can get the same result inside the batch file?

----------------------------

#3 05 Jun 2014 10:24
foxidrive

If you reveal the actual task then people reading may help you with a method that you aren't aware of.

----------------------------

#4 05 Jun 2014 11:00
bucsie


Oh, ok.

I need to rename the files, by adding an extra ".zip" at the end.
I also need to log everything (it's a requirement).
Also, the batch file is called by another program, whenever a file is transferred by it in a directory.

Sometimes the renaming fails because the file is incompletely transferred. This I handle by creating a loop with goto: and retrying a few times.
But some times, and I cannot explain why this happens, I get a "The system cannot find the file specified." error. I cannot get to the root of this error, and the client showed me printscreens that the files are in fact there.

I speculate that perhaps there is a deadlock situation in the calling application because of my requirement to log everything in a file, which forced me to use a separate locking file so only one instance of the batch is running at one time.

This "cannot find the file" error cannot be resolved by retrying in the loop. So what I thought to do, is to call the same program again, after waiting for a few seconds and allow other waiting instances of the program to run.

I hope my thought process so far is clear

So, the script starts by using a lock file to make sure the log is accessible

Code: Select all

@echo %TIME% 
:loggingLock
2>nul (
  >> d:\temp\lock.file (
	@echo %TIME% 
   	call :startRoutine %*
) || ( 
@ping -n 8 -w 1 127.0.0.1 > NUL
goto :loggingLock
)
)
@goto :eof
Then, I compute the current date to use it for the log file name (this I got with help from stackoverflow).
The, I call the renaming routine and send all the log to the log file.
If the renaming fails, it returns an error, so I use start to start a new instance of the same file with the same parameters, after 8 seconds

Code: Select all

:startRoutine

@set excName=%~dpnx0

@Set _Date=%date%
@if "%_Date%A" LSS "A" (Set _NumTok=1-3) Else (Set _NumTok=2-4)
@For /F "TOKENS=2*" %%A In ('REG QUERY "HKCU\Control Panel\International" /v iDate') Do @Set _iDate=%%B
@For /F "TOKENS=2*" %%A In ('REG QUERY "HKCU\Control Panel\International" /v sDate') Do @Set _sDate=%%B
@IF %_iDate%==0 For /F "TOKENS=%_NumTok% DELIMS=%_sDate% " %%B In ("%_Date%") Do @Set _fdate=%%D%%B%%C
@IF %_iDate%==1 For /F "TOKENS=%_NumTok% DELIMS=%_sDate% " %%B In ("%_Date%") Do @Set _fdate=%%D%%C%%B
@IF %_iDate%==2 For /F "TOKENS=%_NumTok% DELIMS=%_sDate% " %%B In ("%_Date%") Do @Set _fdate=%%B%%C%%D

@echo %TIME% 

call :startProgram %* >>"d:\temp\fileRename%_fdate%.log" 2>&1
@echo %TIME% 
@if %ERRORLEVEL% EQU 0  exit /b 0
else  start "renamer retry" /BELOWNORMAL "((@ping -n 8 -w 1 127.0.0.1 > NUL) & ("%excName%" %*))")
exit /b

And then, here is the renaming code. Basically, I try a rename; if it fails, I increase a counter and go back to try again, if not, I exit with errorlevel 0. If 10 retries fail, I return a errorlevel 1

Code: Select all

@echo received params: %*
SETLOCAL
setlocal EnableDelayedExpansion
@set retryNumber=0

set FileName=%~nx2
set FullFileLocation=%~dpnx2
set FileLocation=%~dp2

@echo %TIME% received %FileName% for renaming
:startRenaming

    @set newFileName=%FileName%.zip

    @echo %TIME% renaming %FileName% to %newFileName% 

:renamingCommand
	@takeown /f "%FullFileLocation%"
	rename "%FullFileLocation%" "%newFileName%"  
	@if %ERRORLEVEL% EQU 0  (	
	@takeown /f "%FileLocation%%newFileName%"
	goto :endSuccess
	)
:retryStep
	@set /a retryNumber=%retryNumber%+1
	@echo %TIME% An error occurred during renaming on try %retryNumber%.  
	@if %retryNumber%==10 goto :errEnd
	@echo %TIME% Reevaluating in 5 seconds.
	@ping -n 5 -w 1 127.0.0.1 > NUL
	@goto :renamingCommand

:errEnd
@echo %TIME% Error renaming %FileName% on try %retryNumber%: The file was not renamed. Giving up.
ENDLOCAL
ENDLOCAL
@exit /b 1
:endSuccess
@set retryNumber=0
@echo %TIME% finished renaming %FileLocation% to %newFileName% 
ENDLOCAL
ENDLOCAL
@exit /b 0

Now, this version loops infinitely if the file rename fails each time. I want it to give up some time. Either the next day (I cannot figure how to do that), or (ideally) after a number of 10 more retries.
So, I figured I should count the number of times the filename appears in the log file (or the lock file, I can use whichever) and if it's 10, exit with error code 0, so killing the retrying for that particular file.

So I added the code in my first message, before the `:renamingcommand` label.

I have tried a lot of combinations for this, but none of them runs correctly. The for loop with the 'find' in it, I also tried to perform a 'find > tmp.file' (redirect only the find results to a temporary file) -which is always empty, nothing gets written.
I thought maybe the program can't read the log as well as write in it, so I performed a copy to another temporary file and tried to 'find' on that, but still no result.
I am truly stumped.

I tried to avoid this lengthy description initially, because I thought no-one will have the patience to read this long a question :)

----------------------------

#5 05 Jun 2014 11:34
foxidrive
bucsie wrote:

I tried to avoid this lengthy description initially, because I thought no-one will have the patience to read this long a question :)

It's true in some cases. :)

Do your files or paths ever have spaces or % or & or ! in the names?

Are the paths and names in Latin alphanumeric characters or are there any characters from other languages?

Are there any Unicode characters in any of the filenames?

----------------------------

#6 05 Jun 2014 12:23
bucsie

The paths have spaces. No % or & or ! or even " .
I did not notice any non-standard characters. A typical filename is something like:

424MSOG188531230233334102.swi.SNL50050D174456534041762S

The thing that kills me is that if I run the for command from the log in the command prompt, it works. But inside the batch, it outputs nothing, even though the log states that it expands correctly.

----------------------------

#7 05 Jun 2014 13:55
foxidrive

An immediate problem on a first glance shows this code which will never execute because else needs parentheses:

Code: Select all

@if %ERRORLEVEL% EQU 0  exit /b 0
else  start "renamer retry" /BELOWNORMAL "((@ping -n 8 -w 1 127.0.0.1 > NUL) & ("%excName%" %*))")
exit /b
I think this is a better way of doing it:

Code: Select all

@if %ERRORLEVEL% EQU 0  exit /b 0 
ping -n 8 -w 1 127.0.0.1 > NUL
start "renamer retry" /BELOWNORMAL cmd /c "%excName%" %*
exit /b
Last edited by foxidrive (05 Jun 2014 13:57)

----------------------------

#8 05 Jun 2014 14:14
foxidrive

You also have a mess of endlocal statements, and delayed expansion, which aren't really needed in your code.

This simplifies the action and if it solves the issue then you can add a single setlocal command at the top. exit automatically issues an endlocal.

Code: Select all

@echo received params: %*
@set retryNumber=0

set FileName=%~nx2
set FullFileLocation=%~dpnx2
set FileLocation=%~dp2

@echo %TIME% received %FileName% for renaming
:startRenaming

    @set newFileName=%FileName%.zip

    @echo %TIME% renaming %FileName% to %newFileName% 

:renamingCommand
    	@if %retryNumber% EQU 10 @echo %TIME% Error renaming %FileName% on try %retryNumber%: The file was not renamed. Giving up. & @exit /b 1

	@takeown /f "%FullFileLocation%"
        @set t=%time%
	@rename "%FullFileLocation%" "%newFileName%" || (
          @echo %T% An error occurred during renaming on try %retryNumber%.
          @echo %T% Reevaluating in 5 seconds.
          @ping -n 5 localhost>nul
	    @set /a retryNumber+=1
	    @goto :renamingCommand
	    )   

	@takeown /f "%FileLocation%%newFileName%"

@set retryNumber=0
@echo %TIME% finished renaming %FileLocation% to %newFileName% 
@exit /b 0
----------------------------

#9 05 Jun 2014 14:27
bucsie
foxidrive wrote:

An immediate problem on a first glance shows this code which will never execute because else needs parentheses:
..
I think this is a better way of doing it:

Code: Select all

    @if %ERRORLEVEL% EQU 0  exit /b 0 
    ping -n 8 -w 1 127.0.0.1 > NUL
    start "renamer retry" /BELOWNORMAL cmd /c "%excName%" %*
    exit /b
You are right, it shouldn't execute, but it does! Can't explain why.

I want the ping (wait) to execute in the new started environment, not in this one, to give another thread the chance to run in the meantime, that's why I put it inside the start. Is the cmd /c required?

Could the errant else be messing with the for statement?
I'll try to run without the else (but with the ping inside start) and see what happens.

I have added the setlocal statements after I couldn't make the script work, but will remove them, too.

----------------------------

#10 05 Jun 2014 14:53
bucsie

So, I used your suggestions, and the code looks much cleaner, indeed!

The start command didn't work (it blocked the batch) without the else and cmd /c .
But after I added the cmd /c, it worked like a charm.

If I were to be picky, I'd say that the logging dosn't look so great with this version, because it produces lines like this:

Code: Select all

..>rename -t "long_file_name" "another long filename" || (
 
  
  
  
 
) 
The syntax of the command is incorrect.
16:41:20.72 An error occurred during renaming on try 0.
16:41:20.72 Reevaluating in 5 seconds.
( I added the -t to make the rename fail so I can test it). I'd love to be able to supress all the whitespace, but that's not an important issue.

The important issue is that the for command still doesn't work sad, and I still can't count the running times.

In the original version, I also tried adding an extra parameter to the command line ("%excName%" %variable_that_I_can_work_with% %*) but it didn't get passed through, only the original parameters were accepted ...

----------------------------

#11 05 Jun 2014 14:53
foxidrive
bucsie wrote:

You are right, it shouldn't execute, but it does! Can't explain why.
I doubt very much that it does, from that particular line.

This is what will happen:

Code: Select all

d:\>else start "" cmd /k
'else' is not recognized as an internal or external command,
operable program or batch file.
The only way it will run is if the batch file is called else.bat

It may be that the & is effective but it's inside quotes, and the parentheses are mismatched too so maybe it does launch without the ping running.
I want the ping (wait) to execute in the new started environment, not in this one, to give another thread the chance to run in the meantime, that's why I put it inside the start. Is the cmd /c required?
Try this:

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c "ping -n 8 -w 1 127.0.0.1 > NUL & %excName% %*"
Last edited by foxidrive (05 Jun 2014 15:00)

----------------------------

#12 06 Jun 2014 13:52
bucsie
foxidrive wrote:

Try this:

Code: Select all

    start "renamer retry" /BELOWNORMAL cmd /c "ping -n 8 -w 1 127.0.0.1 > NUL & %excName% %*"
This does not work if the path to the executable contains spaces and if the parameters contain ". Which they always do :(.
And I still cannot find a solution to the original problem.

----------------------------

#13 06 Jun 2014 16:55
foxidrive

Did you try adding quotes?

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c "ping -n 8 -w 1 127.0.0.1 > NUL & "%excName%" %*"
----------------------------

#14 10 Jun 2014 09:03
bucsie
foxidrive wrote:

Did you try adding quotes?

Code: Select all

    start "renamer retry" /BELOWNORMAL cmd /c "ping -n 8 -w 1 127.0.0.1 > NUL & "%excName%" %*"

I have tried all the combinations that came to my mind with quotes and parentheses. None of them worked correctly.

For your example, as well as for:

Code: Select all

start "renamer retry" /BELOWNORMAL (ping -n 8 -w 1 127.0.0.1 > NUL) & (cmd /c "%excName%  %cc%")
where %cc% = %*
The scripts get blocked at the first call. (the log only displays the first "start" call and then I assume it's a deadlock)

For other attempts, like for the combination:

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c ""ping -n 8 -w 1 127.0.0.1 > NUL" & "(%excName% %cc%)""
or

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c ""ping -n 8 -w 1 127.0.0.1 > NUL" & "%excName%  %*""
weirdly, the order of the parameters appears scrambled in the log file:

Code: Select all

>start "renamer retry" /BELOWNORMAL cmd /c ""ping -n 8 -w 1 127.0.0.1  -p1 "pval1" -p2 "pval2" -p58 "")"" 1>NUL" & "(D:\temp\renameScript.bat 

start "renamer retry" /BELOWNORMAL cmd /c ""ping -n 8 -w 1 127.0.0.1  0 -p1 "pval1" -p2 "pval2" -p58 """" 1>NUL" & "D:\temp\renameScript.bat 
----------------------------

#15 10 Jun 2014 18:23
Honguito98

Try escape the '&' and '>' with a caret '^'

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c ping -n 8 -w 1 127.0.0.1 ^> NUL ^& %excName% %cc%
if cc var contains any '&', you need escape by 3 carets, example:

Code: Select all

set "cc=%*"
set "cc=%cc:&=^^^&%"
if execName var contains spaces you need add some quotes:

Code: Select all

set execName="c:\my program.exe"
:: if execName contains & symbol:
set "execName=%execName:&=^^^&%"
Last edited by Honguito98 (10 Jun 2014 19:07)

.::{Honguito98}::.

----------------------------

#16 11 Jun 2014 08:46
bucsie

Thanks for the suggestion. Have tried, again, many variants.
There are no & characters in cc, but the parameters contain ". so they are like this -pi "pi value" where i goes from 1 up to 58.

So I've put

Code: Select all

@set cc=%*
@set cc=%cc:"=^^^"%
@set rc=%1
echo %rc% | findstr /r /c:"^[0-9][0-9]*" && ( 
   if %rc% GTR 5 (exit) else (set /a rc=%rc% + 1 )
) || (
set rc=0
)
to increment the first parameter if it is a number, so that I can cap the number of recursive calls.

And I have tried a lot of variants:

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c ping -n 8 -w 1 127.0.0.1 ^> NUL ^& %excName% %rc% %cc%
results in blocking, and in my task manager I have both the original command call and the following:

Code: Select all

cmd  /c ping -n 8 -w 1 127.0.0.1 > NUL & D:\temp\renameScript.bat 0 -p1 "p1value" -p2 "p2.val!x" -p3 "" 
The same happens if I use ()

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c (ping -n 8 -w 1 127.0.0.1 ^> NUL ^& %excName% %rc% %cc%)


I tried with ", as is the spec for cmd

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c "ping -n 8 -w 1 127.0.0.1 ^> NUL ^& %excName% %rc% %cc%"
which displays in the log file as:

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c "ping -n 8 -w 1 127.0.0.1 ^> NUL ^& D:\temp\renameScript.bat 0 -p1 ^"p1value" -p2 "p2.val!x" -p3 """ 
But this fails, so it does not block the recursive call of the .bat file and the %rc% param is not transmitted

The same goes for

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c ""ping -n 8 -w 1 127.0.0.1 ^> NUL" ^& "%excName% %rc% %cc%""
which translates to

Code: Select all

start "renamer retry" /BELOWNORMAL cmd /c ""ping -n 8 -w 1 127.0.0.1 > NUL" ^& "D:\temp\renameScript.bat 0 -p1 "p1value" -p2 "p2.val!x"" -p3 """" 
(Tried with &, ^&, ^^&, ^^^& - all with the same result).

At this point, I am hoping it is an issue with separators and special characters, not that I am trying to do the impossible.

Last edited by bucsie (11 Jun 2014 08:50)
Post Reply