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
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
----------------------------
#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
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
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
----------------------------
#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
You are right, it shouldn't execute, but it does! Can't explain why.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
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.
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
I doubt very much that it does, from that particular line.bucsie wrote:
You are right, it shouldn't execute, but it does! Can't explain why.
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.
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.
Try this: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?
Code: Select all
start "renamer retry" /BELOWNORMAL cmd /c "ping -n 8 -w 1 127.0.0.1 > NUL & %excName% %*"
----------------------------
#12 06 Jun 2014 13:52
bucsie
This does not work if the path to the executable contains spaces and if the parameters contain ". Which they always do .foxidrive wrote:
Try this:
Code: Select all
start "renamer retry" /BELOWNORMAL cmd /c "ping -n 8 -w 1 127.0.0.1 > NUL & %excName% %*"
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%")
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%)""
Code: Select all
start "renamer retry" /BELOWNORMAL cmd /c ""ping -n 8 -w 1 127.0.0.1 > NUL" & "%excName% %*""
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%
Code: Select all
set "cc=%*"
set "cc=%cc:&=^^^&%"
Code: Select all
set execName="c:\my program.exe"
:: if execName contains & symbol:
set "execName=%execName:&=^^^&%"
.::{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
)
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%
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 ""
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%"
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 """
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%""
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 """"
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)