Page 1 of 1

Exiting a batch from a function

Posted: 02 Mar 2016 07:39
by sambul35
Hi,

I found here detail D.Benham's analysis showing that exiting a batch from withing a function by adding a syntax error has bad side effects, because the fatal syntax error terminates batch processing without the implicit ENDLOCAL! I seems to experience random errors in my project due to this fact. Trying to find a SHORT code allowing to exit a batch from a function correctly.

Some folks posted short code snippets to this effect. One was offered by Max Suslov:

Code: Select all

@echo off

:: comment next line if you want to export any local variables in caller environment
setlocal

set FLAG=1
rem Do something
call :interactive_check

set FLAG2=2
rem Do something
call :interactive_check

goto :eof

:interactive_check
if errorlevel 1 (
    echo.
    echo /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\
    echo Error in compilation process... exiting
    echo /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\
    (goto) 2>nul & endlocal & exit /b %ERRORLEVEL%
) else (
    echo Continuing to next step
)
goto :eof



The other by Charles Godwin:

Code: Select all

@echo off
call :error message&if errorlevel 1 exit /b %errorlevel%<
@echo continuing
exit /b 0
:error
@echo in %0
@echo message: %1
set yes=
set /p yes=[no]^|yes to continue
if /i "%yes%" == "yes" exit /b 0
exit /b 1


Can someone explain, if and why these solutions allow to terminate the batch cleanly as suggested by their authors, as opposed to terminating by introducing a syntax error? Unfortunately, the above suggestions were not commented by batch experts, but they seem to offer elegant solutions - correct :?:

Re: Exiting a batch from a function

Posted: 02 Mar 2016 10:13
by dbenham
No, not correct :!:

The Charles Godwin "solution" does not exit the script from within the function at all. The call conditionally resets the ERRORLEVEL to 0 and then returns normally. The EXIT from the script actually occurs on the CALL line if and only if the returned ERRORLEVEL is greater than 0.

The Max Suslov "solution" sort of works, but it is not a generic solution. It only works if the CALL is only one level deep. It would have to be modified if there were a CALL that in turn issues a CALL. The number of (GOTO) 2>NUL required is dependent on the CALL level.

There is another complicating factor with using (GOTO) 2>NUL - Already parsed commands will still execute, which may not be desirable. And if enough (GOTO) are issued to exit batch processing completely, then those parsed commands are executed in a command line context rather than a batch context:

Code: Select all

@echo off
call :sub1 & call echo After :sub1, %%notExists%% means command line context
echo This is not executed
exit /b

:sub1
call :sub2 & call echo After :sub2, %%notExists%% means command line context
echo This is not executed
exit /b

:sub2
call :exit & call echo After :exit, %%notExists%% means command line context
echo This is not executed
exit /b

:exit
(
  (goto)
  (goto)
  (goto)
  (goto)
  call echo Within :exit, %%notExists%% means command line context
) 2>nul
--OUTPUT--

Code: Select all

Within :exit, %notExists% means command line context
After :exit, %notExists% means command line context
After :sub2, %notExists% means command line context
After :sub1, %notExists% means command line context


The (GOTO) behavior was first documented in English at viewtopic.php?f=3&t=6491. Read the entire thread, but in particular, make sure you read jeb's post at viewtopic.php?f=3&t=6491#p41579.

If you want a generic solution to immediately and cleanly terminate batch processing from within a CALLed routine at any CALL depth, then I suggest you use the programmatic Ctrl-C method posted at http://stackoverflow.com/a/25474648/1012053. It is not simple code, but I am not aware of any other clean method to do this.

You could modify the (GOTO) method to dynamically issue the correct number of (GOTO), but you would still need the CTRL-C method to prevent already parsed commands from executing.

However, the (GOTO) method is extremely useful :!:
See http://stackoverflow.com/q/31445330/1012053 for a demonstration of how it can be used to develop effective exception handling within batch. The exception handling takes termination of a batch script to another level, allowing you to perform cleanup operations before the script terminates. That link also provides additional links to other uses for (GOTO).


Dave Benham

Re: Exiting a batch from a function

Posted: 02 Mar 2016 13:51
by sambul35
dbenham wrote:No, not correct :!:
Already parsed commands will still execute, which may not be desirable.


Thanks Dave for the analysis. Pretty useful application of what seems to be a Cmd bug discovered by chance, to address another Cmd deficiency - inability to exit a batch from a function. One bug cancels another (to some degree). :mrgreen:

Does your above note about already passed commands refers to a Multilevel CALL only? If not, what would be an example in case of a one level CALL? Jeb suggested to add Exit /B at the end to remove residual cache. Does it give the same result as cancelling already passed commands, or what would be significance of that action?

Looking through the original thread linked by siberia-man, found an interesting snippet that was the subject of the original discovery:

Code: Select all

@echo off

if not defined caller (
    (goto) 2>nul
    call set "caller=%%~f0"

    call "%~f0" %*
)

:: Useful Code
echo:%*


Not sure I understand the above code. While (goto) is now cleared, what these 2 statements below it actually do, and why they're needed?

Re: Exiting a batch from a function

Posted: 03 Mar 2016 19:03
by sambul35
That now works for me to exit a batch from a one level deep function:

Code: Select all

(goto) 2>nul & endlocal & echo Exiting & exit /b


Also, when using several one level functions in a batch consecutively, I had to add

Code: Select all

(goto) 2>nul & exit /b
at the end of each, if they were CALLed one immediately after another, whether it was the same function repeated with different parameters, or different functions. Otherwise my batch wouldn't work, despite these functions didn't require exit from the batch itself. However, it was enough to end each function with exit /b only, if there were other commands executed between CALLing these functions.

Re: Exiting a batch from a function

Posted: 23 Apr 2016 16:19
by sambul35
Wanted to amend the discoveries of (goto) 2>nul magic.

I use it frequently to exit a function like this, which is called from somewhere in a batch with a series of arguments, and may require redirection depending on some value comparison:

Code: Select all

:function1
------some code----------
(goto) 2>nul & goto %~4
exit /b

:function2
------enclosed or multiple IF code----
set "link=%~5"
------more IF statements--------------
set "link=%~2"
------------------------------------------
(goto) 2>nul & if not "!link!"=="" (goto !link!)
exit /b


However, the following factors may affect the code work in functions with multilevel enclosed IF ELSE IF statements:
- define all variables explicitly, including those seemingly not "used" in a current run, to account for CMD processing rules
- use delayed expansion !var! for variables that may change between calls to the function
- place each opening & closing IF statement bracket on a separate line if needed
- add extra line (goto) 2>nul [& goto %~N] to each branch of IF ELSE IF command with exit from the function if needed
- limit the depth and total number of enclosed IF statements in a function

Still wonder, what does this code exactly do?