Reputation: 633
I'm trying to simulate exception handling in batch. I know theres a forum post on DosTips that goes extensively into this topic and even provides a script that does some of what you would expect. Since this is a work project I can't use their solution so I tried my own hand at this.
I've tried to implement what I'll call the "poor man's try catch" which is really just a FOR
loop that runs batch commands line by line and if the errorlevel
is above 0, the line is caught by the loop in an IF
from the DO
clause. For example, I tried writing this in batch:
@echo OFF
SET /A "LineCount=1"
FOR /F "tokens=* delims=" %%A IN ('SET "VarATest^=something" ^^ SET "VarBTest^=somethingelse" ^^ SE "VarCTest^=syntaxerror"') DO (SET /A "LineCount=1+!LineCount!" & IF !ERRORLEVEL! EQU 1 (echo errorlevel was: "!ERRORLEVEL!" and line was "!LineCount!") ELSE (echo line worked))
pause
Basically what the above is supposed to do is, initialize a LineCount
variable, run the three batch commands in the IN
clause (seperated by the ^
line enders) and for each line, increment the LineCount
and check to see if the line caused an errorlevel
of anything higher than 1 (!ERRORLEVEL! EQU 1
has that odd behavior). The third line has an obvious syntax error, the SET
is spelled SE
which should cause an errorlevel
higher than 0. The point is demonstration, this could be refined but right now its simply for proof of concept.
Unfortunately when I ran this code it didn't run anything as expected, instead all I get is the pause output but no errors. I don't beleive its running the IN
clause line by line as I expected so my question is this:
A. Is it possible to run batch commands line by line in the IN
clause of a FOR
loop to simulate a try catch? Obviously this means using delayed expansion and escape characters more frequently, but it'd be cool to have this rudimentary functionality.
B. If it is possible then what is the proper way to make the input of the IN
clause be raw text but also multiline at the same time. For example, the IN
clause right now is:
(SET "VarATest^=something" ^^ SET "VarBTest^=somethingelse" ^^ SE "VarCTest^=syntaxerror")
which I want to be functionally similar to the regular batch code of:
SET "VarATest=something"
SET "VarBTest=somethingelse"
SE "VarCTest=syntaxerror"
Where the third line obviously causes an error. I don't want to use another file if possible so no tempfiles that hold the try block code. Would I use the ^
line ender? I tried ^^
thinking I needed to escape the first line ender. I also tried newlines where the ^^
were but that didn't work either. I tried modifying delims to try and split the commands that way, maybe run them each that way but still I get no output.
edited out noise, tried answer provided which was extremely helpful but unfortunately did not yeild the solution however after tinkering I came up with this:
@echo OFF && setlocal enableextensions enabledelayedexpansion
SET lf=^&echo/^^
SET /A "LineCount=0"
FOR /F "tokens=*" %%A IN ('call!lf!SET "VarATest^=something"!lf!SET "VarBTest^=somethingelse"!lf!SET "VarCTest^=syntaxerror"!lf!') DO (SET /A "LineCount=1+!LineCount!" && echo ER: "!ERRORLEVEL!" or "%ERRORLEVEL%" A: "%%A" && IF !ERRORLEVEL! NEQ 0 (echo errorlevel was: "!ERRORLEVEL!" and line was "!LineCount!") ELSE (echo line worked = !LineCount!))
pause >nul
echo/!LineCount! && pause >nul
now this is mostly the answer provided with a few caveats. Adding in the "tokens=*"
part actually gets the full SET
line. The answer provided the linefeed mechanism. With experimentation I observed that a call!lf!
was needed BEFORE the first command in the "string". I have absolutely no idea why but it doesn't work any other way because the FOR
loop seems to want to skip the first argument. I got a printout of each command line in the string through %%A. fantastic! The only problem though, is that errors are not caught when they should be. ERRORLEVEL
does not seem to change when I change the last command string from
SET "VarCTest^=syntaxerror"
to:
ST "VarCTest^=syntaxerror"
or any similar variations that would cause an error to be thrown.
The issue is the SET
commands are never actually ran. Running an echo of them after the FOR
loop reveals this. I'm going to try and get them running
Upvotes: 1
Views: 2248
Reputation: 2565
If I understood well your question:
@echo off & setlocal EnableDelayedExpansion & color 0a
for /f %%a in ('forfiles /p "%~dp0." /m "%~nx0" /c "cmd /c echo 0x1B"') do set "Esc=%%a"
SET lf=^&echo/^^
SET /A "LineCount=1"
FOR /F %%A IN ('SET /p "VarATest=something"^<nul!lf!SET /p "VarBTest=somethingelse"^<nul!lf!SET /p "VarCTest=syntaxerror"^<nul!lf!') DO (
SET /A "LineCount=1+!LineCount!"
IF "!ERRORLEVEL!" == "1" (
echo errorlevel was: "!ERRORLEVEL!" and line was "!LineCount!"
) ELSE (
echo line worked = !LineCount!
)
echo/!Esc![4m!Esc![31mError on "!LineCount!" Error code !ERRORLEVEL!!Esc![0m
)
pause >nul
echo/!LineCount! & pause >nul
Note: After SET lf=^&echo/^^
, the 2 line below are needed to do the "fake break line".
Upvotes: 1
Reputation: 67216
This is not really an answer to this question, but it is too large to fit in a comment...
&
character (or its variants). This is a very basic Batch-file point.^
character is not a "line ender" (where did you read that?). It is used to escape the next character. When the next character is the end of the current line, then the current line "continues" into the next one (with certain restrictions).for /F
command is run all commands placed in IN
clause first and, after the last command had ended, start to iterate the commands placed in the for-body using the lines generated in IN
part.for /F
uses to execute the commands in IN
clause is via a child cmd.exe
process. There is no way to directly pass any errorlevel value from this cmd.exe
to the commands placed in the for-body, excepting via a text file.cmd.exe
is executed in the command-line context, NOT in the Batch-file context, so a special management is required in order to modify the behaviour of the commands placed in the IN
part.EDIT: I added code that supposedly answers the question.
I really don't understand what is the purpose of this topic and I don't understand many (almost all) of the descriptions you use to explain what you are doing... In despite of your extensive explanations (with a lot of non-technicall words and distracting "chapter titles"), you had not clearly described what you want as final result and the way you want go get such a result. For this reason I can only guess... So I guess you are looking for something similar to this:
@echo off
setlocal EnableDelayedExpansion
SET /A "LineCount=0"
FOR %%A IN ( "SET ""VarATest=something"""
"ST ""VarBTest=syntaxerror"""
"SET ""VarCTest=somethingelse""" ) DO (
SET /A "LineCount+=1"
SET "StringImUsing=%%~A"
SET "StringImUsing=!StringImUsing:""="!"
!StringImUsing! >nul 2>&1
IF !ERRORLEVEL! NEQ 0 (
echo Line was !StringImUsing!
echo Error on "!LineCount!" Error code !ERRORLEVEL!
VER >nul
) ELSE (
echo line worked = !LineCount!
)
)
echo A: "%VarATest%"
echo B: "%VarBTest%"
echo C: "%VarCTest%"
VER is the command used to reset ERRORLEVEL to zero as described below Table 4 at this answer. It is a much simpler and standard command that the strange (call )
trick...
Upvotes: 1
Reputation: 633
So I finally got the code to this point. It took a ton of experimentation, and a little trouble with escaping double quotes and resetting the errorlevel, but the code I got to work was this:
@echo OFF && setlocal enableextensions enabledelayedexpansion
SET lf=^&echo/^^
SET /A "LineCount=0"
FOR /F "tokens=* delims=" %%A IN ('call!lf!ST ""VarATest^=something""!lf!SET ""VarBTest^=somethingelse""!lf!SET ""VarCTest^=syntaxerror""!lf!') DO (SET /A "LineCount=1+!LineCount!" && call :RunMyCommandSubroutine "%%A")
echo A: "!VarATest!"
echo A: "%VarATest%"
echo B: "!VarBTest!"
echo B: "%VarBTest%"
echo C: "!VarCTest!"
echo C: "%VarCTest%"
pause >nul
goto:EOF
:catchsubroutine
echo [43;97mError on "!LineCount!" Error code !ERRORLEVEL![0m
goto:EOF
:RunMyCommandSubroutine
SET "StringImUsing=%~1"
SET "StringImUsing=%StringImUsing:""="%"
%StringImUsing% >nul 2>&1
IF !ERRORLEVEL! NEQ 0 (echo [43;97mLine was %StringImUsing%[0m && call :catchsubroutine) ELSE (echo line worked = !LineCount!)
(call )
goto:EOF
So to test it, you can create a typo in the SET
command strings in the IN
portion of the for loop and see that that specific "line" is marked in Yellow as an error (e.g. try making SET
into ST
). You're all probably wondering what the (call )
is for in the RunMyCommand
subroutine. Its for resetting the ERRORLEVEL
to 0 but not permanently. I was falling for the mistake of SET
ing the errorlevel
myself (using SET /A "ERRORLEVEL=0"
), which creates a copy and doesn't use the system variable errorlevel, which I observed as it remained 0 after I 'reset it' even when an error occurred. So after fixing that, and escaping the double quotes around the SET argument with ""
, I also needed to make a subroutine because putting the check for errorlevel
in the DO
of the IF
caused the check to be one line too late, because when the FOR
loop gets an error in the DO
clause, it seems to stop everything else and move on to the next argument. I also figured out that you can just use the command string itself to 'run' the command (e.g. if I have the command in %%A
just type %%A
on a seperate line and the command will run normally). So we have a proper check, a proper errorlevel
reset, a proper way to run our multiline input strings, and I even put in some ANSI codes to make the UI look nice.
All in all pretty cool! I have no idea if this can work with more complicated examples like nested IF
s FOR
s or other weird commands, but It should check for any error that causes the errorlevel
to be anything but zero. I also have a catch subroutine that allows for cleanup almost immediately after the erroring line is ran. This allows for some cleanup operations. So hey! It turns out it was (sorta) possible!
Still big thanks to kapputz. His answer wasn't the solution but it helped ALOT with so many different things I needed to understand.
Upvotes: 1