exit function and preserve variable over endlocal barrier

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: exit function and preserve variable over endlocal barrier

#16 Post by Aacini » 22 Aug 2016 06:17

thefeduke wrote:I am risking some basic questions on scope and barriers. I have been wrestling with CHDIR and PUSHD.

Is there such a concept as "implied variables" that would apply to these commands? ...

I concluded that %CD% had been localized.


Yes. SETLOCAL/ENDLOCAL commands have an undocumented behavior that saves/restores the current directory as if it was a standard environment variable:

Code: Select all

@echo off

echo Current directory: %cd%

rem SETLOCAL command "saves" the current directory:
setlocal

md test
cd test
echo Current directory after changed it: %cd%

rem ENDLOCAL command restores last "saved" directory:
endlocal
echo Current directory after ENDLOCAL:   %cd%


rem Check this funny error:
echo/
cd test
echo Now in %cd%
setlocal
cd ..
echo Back to %cd%
rd test
echo The error:

The second example show the "Path not found" error when the Batch file ends, so an implicit ENDLOCAL command is executed and the current directory is tried to be changed to a non existent directory! This problem was fixed in posterior Windows versions, like in 8.1; I don't remember what was the last Win version that presented this error, but I think was XP.

Further details at this post.

Antonio

random
Posts: 2
Joined: 28 Apr 2017 10:03

Re: exit function and preserve variable over endlocal barrier

#17 Post by random » 28 Apr 2017 11:04

@dbenham

I'm using the RETURN.BAT Version 2.0 you posted in this thread to return a variable with the result from a script doing some string manipulation.

The problem is that sometimes the result end up as an empty string. When it does, RETURN.BAT does not return an empty variable, but rather a variable containing %=%

I made a few changes to your test script from the same post to show the issue:

Code: Select all

@echo off
setlocal disableDelayedExpansion
call return init
for /f %%C in ('copy /z "%~f0" nul') do set "\r=%%C"
set ^"\n=^

^"

set test1="This & that" ^& the other thing
set test2="This & that" ^& the other thing!
set test3="&<>|%%^" ^&^<^>^|%%^^
set test4="&<>|%%^!" ^&^<^>^|%%^^!
setlocal enableDelayedExpansion
set "test5=!\n!Line One!\n!hidden!\r!Line Two"
set "test6="

set "err1=0"
set "err2=1"
set "err3=2"
set "err4="
set "err5="
set "err6=1"

setlocal disableDelayedExpansion
echo Delayed expansion is DISABLED
:loop
echo ------------------------------------------
for /l %%N in (1 1 6) do (
  set "result="
  call :test test%%N result err%%N
  call set "err=%%errorlevel%%"
  setlocal enableDelayedExpansion
  echo result=!result!
  echo errorlevel=!err!
  endlocal
)
if "!!" equ "" exit /b
echo(
echo(
setlocal enableDelayedExpansion
echo Delayed expansion is ENABLED
goto :loop


:test
setlocal enableDelayedExpansion
set rtn=!%1!
echo(
echo  %1=!rtn!
call return rtn %2 !%3!


The only changes from the version you posted are an added test6 with a blank/undefined variable:

Code: Select all

set "test6="
set "err6=1"

And changed 5 to 6 in this for loop:

Code: Select all

for /l %%N in (1 1 6) do (


I also added a closing " to this line. It seemed like it was missing one to me. Not sure if you forgot or if there is some reason for it I don't understand, but the result seems unaffected.

Code: Select all

set "test5=!\n!Line One!\n!hidden!\r!Line Two"


Output:

Code: Select all

Delayed expansion is DISABLED
------------------------------------------

 test1="This & that" & the other thing
result="This & that" & the other thing
errorlevel=0

 test2="This & that" & the other thing!
result="This & that" & the other thing!
errorlevel=1

 test3="&<>|%^" &<>|%^
result="&<>|%^" &<>|%^
errorlevel=2

 test4="&<>|%^!" &<>|%^!
result="&<>|%^!" &<>|%^!
errorlevel=0

 test5=
Line One
Line Two
result=
Line One
Line Two
errorlevel=0

 test6=
result=%=%
errorlevel=1


Delayed expansion is ENABLED
------------------------------------------

 test1="This & that" & the other thing
result="This & that" & the other thing
errorlevel=0

 test2="This & that" & the other thing!
result="This & that" & the other thing!
errorlevel=1

 test3="&<>|%^" &<>|%^
result="&<>|%^" &<>|%^
errorlevel=2

 test4="&<>|%^!" &<>|%^!
result="&<>|%^!" &<>|%^!
errorlevel=0

 test5=
Line One
Line Two
result=
Line One
Line Two
errorlevel=0

 test6=
result=%=%
errorlevel=1


As you can see test 6 fails to return an empty string with or without delayed expansion.

I was hoping you would be able to fix that issue so I can skip testing for %=% after each call. Apart from returning an empty string I have not found any problems. I have successfully returned stuff like a single space and unicode strings etc.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: exit function and preserve variable over endlocal barrier

#18 Post by dbenham » 29 Apr 2017 08:24

@random - Great first post to this site :!: :D
Thanks for your effort.

That critical test for undefined result should have been obvious, but somehow I missed it :oops:
The fix just required a couple extra IF DEFINED statements.

I've updated the post with the fixed v2.1, and incorporated your additional test into the test plan.


Dave Benham

random
Posts: 2
Joined: 28 Apr 2017 10:03

Re: exit function and preserve variable over endlocal barrier

#19 Post by random » 29 Apr 2017 10:54

Perfect. Thank you. :D

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: exit function and preserve variable over endlocal barrier

#20 Post by dbenham » 21 Aug 2017 13:19

I've updated RETURN.BAT to support return of multiple values.

Note that the total combined length of all returned variable names and values must be less than ~3.8k bytes. This was the case in earlier versions as well. But the ability to return multiple values makes the limitation more significant.


Dave Benham

pieh-ejdsch
Posts: 240
Joined: 04 Mar 2014 11:14
Location: germany

Re: exit function and preserve variable over endlocal barrier

#21 Post by pieh-ejdsch » 14 Apr 2018 18:03

I once played around with the return.
The exclamation point I have replaced in between - as well as the caret.
Since I'm not quite sure why the exclamation mark in two lines is also available, I left the time as it is.
Only one variable is used to create variables in delayed and disabled expansion.
This also eliminates the need to check for delayed expansion.

I leave the version number to Dave.

Code: Select all

::RETURN.BAT Version version ??
@if "%~2" equ "" (goto :return.special) else goto :return
:::
:::call RETURN  ValueVar  ReturnVar  [ErrorCode]
:::  Used by batch functions to EXIT /B and safely return any value across the
:::  ENDLOCAL barrier.
:::    ValueVar  = The name of the local variable containing the return value.
:::    ReturnVar = The name of the variable to receive the return value.
:::    ErrorCode = The returned ERRORLEVEL, defaults to 0 if not specified.
:::
:::call RETURN "ValueVar1 ValueVar2 ..." "ReturnVar1 ReturnVar2 ..." [ErrorCode]
:::  Same as before, except the first and second arugments are quoted and
:::  space delimited lists of variable names.
:::
:::  Note that the total length of all assignments (variable names and values)
:::  must be less then 3.8k bytes. No checks are performed to verify that all
:::  assignments fit within the limit. Variable names must not contain space,
:::  tab, comma, semicolon, caret, asterisk, question mark, or exclamation point.
:::
:::call RETURN  init
:::  Defines return.LF and return.CR variables. Not required, but should be
:::  called once at the top of your script to improve performance of RETURN.
:::
:::return /?
:::  Displays this help
:::
:::return /V
:::  Displays the version of RETURN.BAT
:::
:::
:::RETURN.BAT was written by Dave Benham and DosTips user jeb, and was originally
:::posted within the folloing DosTips thread:
:::  http://www.dostips.com/forum/viewtopic.php?f=3&t=6496
:::
::==============================================================================
::  If the code below is copied within a script, then the :return.special code
::  can be removed, and your script can use the following calls:
::
::    call :return   ValueVar  ReturnVar  [ErrorCode]
::
::    call :return.init
::

:return  ValueVar  ReturnVar  [ErrorCode]
:: Safely returns any value(s) across the ENDLOCAL barrier. Default ErrorCode is 0
setlocal enableDelayedExpansion
if not defined return.LF call :return.init
if not defined return.CR call :return.init
set "return.Cmd="
set "@=%%5"
set "allReturn.vars=%~2"
for %%L in ( "!return.LF!" ) do for %%C in ( "!return.CR!"
 ) do for %%a in (%~1) do for /f "tokens=1*" %%b in ("!allReturn.vars!") do (
  set "return.var=!%%a!"
  if defined return.var (
    set "return.var=!return.var:%%=%%3!"
    set "return.var=!return.var:%%~L=%%~1!"
    set "return.var=!return.var:%%~C=%%2!"
    set "return.var=!return.var:"=%%4!"
    set "return.var=!return.var:^=%%6!"
    call set "return.var=%%return.var:^!=^!@^!%%"
    for /f delims^=^ eol^= %%D in ("!return.var!") do set "return.var=%%D"
  )
  set "return.Cmd=!return.Cmd!!return.LF!set "%%b=!return.var!"^!"
  set "allReturn.vars=%%c"
)

set "err=%~3"
if not defined err set "err=0"
for %%1 in ("!return.LF!") do for /f "tokens=1-3" %%2 in (^"!return.CR! %% "") do (
  (goto) 2>nul
  (goto) 2>nul
  set "%% =1 ^^^! ^^^^"
  for /f "tokens=2-3" %%5 in ("!%% ! ^") do ( %return.Cmd%
  )
  set "%% ="
  if %err% equ 0 (call ) else if %err% equ 1 (call) else cmd /c exit %err%
)

:return.special
@if /i "%~1" equ "init" goto return.init
@if "%~1" equ "/?" (
  for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
  exit /b 0
)
@if /i "%~1" equ "/V" (
  for /f "tokens=* delims=:" %%A in ('findstr /rc:"^::RETURN.BAT Version" "%~f0"') do @echo %%A
  exit /b 0
)
@>&2 echo ERROR: Invalid call to RETURN.BAT
@exit /b 1

:return.init  -  Initializes the return.LF and return.CR variables
set ^"return.LF=^

^" The empty line above is critical - DO NOT REMOVE
for /f %%C in ('copy /z "%~f0" nul') do set "return.CR=%%C"
exit /b 0

PS. check out the variable in: return.SetDelayed with echo - where does the "hidden" come from?

[edit]
subroutine :return.setDelayed is deleted
reduced to "call set" and "for /f "
[/edit]

Phil
Last edited by pieh-ejdsch on 25 Apr 2018 13:16, edited 1 time in total.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: exit function and preserve variable over endlocal barrier

#22 Post by dbenham » 19 Apr 2018 18:00

Hi Phil.

The FOR /F loop to handle conditional escaping of ! and ^ is very clever, and the general concept of using 1 return variable instead of 2 is attractive. I did some tests, and your version does seem to work.

But your code is inconsequentially longer, and doesn't seem to perform noticeably faster. The logic is clever, but I find it a bit harder to follow. In my version I intentionally prefix all temporary, local variables with return. so that it is extremely unlikely to ever collide with a user supplied input variable name. You abandoned that strategy by using allReturn.vars, but that is easily fixed by changing to return.vars. Both of our uses of err is after the danger of name collision is past, so it is OK.

In summary, I don't see any compelling reason to switch. But maybe I am missing something :?:


Dave Benham

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: exit function and preserve variable over endlocal barrier

#23 Post by Ed Dyreen » 19 Apr 2018 18:34

Greetings Dave,

I see that during my absence you've made some remarkable improvements to your return function.

This line I find of particular interest

Code: Select all

set "return.var=%return.var:!=!delayed!%" !
I find it odd to see that readtime expansion could succeed if it has nested runtime expansion which based on my current knowledge can never succeed. Can you or anyone link me to the dostips topic where this was first discussed here on dostips ?


Thanks in advance.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: exit function and preserve variable over endlocal barrier

#24 Post by dbenham » 19 Apr 2018 18:52

Hi Ed.

That line is from Phil (pieh-ejdsch), not me.

There is nothing mysterious about it.

Starting value: "return.var=Hello world!"

Starting statement: set "return.var=%return.var:!=!delayed!%" !

After percent expansion: set "return.var=Hello world!delayed!" !

After delayed expansion: set "return.var=Hello world%5"

And then the SET is executed.

Post Reply