批处理用retLocals、endlocal宏跨setlocal传含感叹号路径时异常
批处理跨setlocal环境屏障传值故障
实现目标
- 采用rtnLocal、endlocal类返回宏方案,跨setlocal环境屏障传递数千个包含
!特殊符号的文件路径与文件名 - 选择宏方案的核心原因:相较于call调用函数、外部批处理文件的实现方式,宏的执行效率更高,该经验结论来自dbendham、jeb、Aacini等批处理领域开发者的公开技术分享
- 参考依据:批处理社区公开的、可在setlocal屏障间传递变量同时保留感叹号的成熟实现思路
故障现象
- 自行编写的测试样例中宏无法正常运行,无法定位故障根因,曾怀疑Win10后续版本的系统逻辑调整导致原有公开方案失效
- 复现逻辑:在嵌入
retLocals宏的第2层setlocal之后,嵌套多层模拟setlocal层级开展测试 - 异常表现:无论将
retLocals的跨层深度参数调整为*2、*4等数值(仅当设置的endlocal层数超过现有setlocal总数时,变量会被直接设置到批处理外的系统命令行层级),都无法将内层变量值传递到嵌套结构外的目标setlocal层级 - 已完成的前置修正:调整代码结构,保证setlocal与endlocal的数量完全平衡
完整测试代码
@echo OFF Setlocal EnableExtensions DisableDelayedExpansion SETlocal EnableExtensions DisableDelayedExpansion ::Return V2.0 - 2016 -jeb @rem 另一核心问题是尝试跨endlocal屏障传递变量时(例如 endlocal & set "RetVar2=%modTestVar%"),该操作实现复杂度较高 @rem @rem 这是专门用于跨屏障传值的批处理宏,调用语法参考 %endlocal% modTestVar if "!" equ "" ( >&2 echo *** 'retLocals' 宏必须在'disableDelayedExpansion'模式下加载 exit /b 1 ) set LF=^ set "\n=^^^%LF%%LF%^%LF%%LF%^^" @rem 单回车字符0x0D(仅在edx模式下可通过!CR!引用) for /F "usebackq delims= " %%C in (`copy /z "%~f0" nul`) do set "CR=%%C" :: 用于跨endlocal屏障返回多个不含换行符的变量的宏 :: :: 语法: %retLocals%[*cnt] out1[=in1] [, out2[=in2] [, out3[=in3]] ...] :: - 'out#' (#=1,2,3,...) 是需要在外层上下文设置的目标返回变量 :: - 'in#' 是需要传递值的内层上下文本地变量 :: - 可选的 [=in#] 缺省为 'out#=out#'(内外变量同名) :: - 可选的 [*cnt] 是嵌套深度,默认值'*1'对应跨1层endlocal调用 :: - 逗号','、等号'='周边的空格均可省略 :: :: 示例: %retLocals% var 将内层'var'跨1层endlocal传递 :: 到外层上下文的同名'var'变量 :: :: %retLocals%*2 out1=in1, out2 将'in1'、'out2'跨2层endlocal :: 传递到外层上下文的'out1'、'out2'变量 :: :: %retLocals%*0 out=in 同上下文内将'in'的值赋值给'out' :: 效果类似'set out=%in%',但可安全处理 :: 嵌入式引号、特殊字符(换行符除外) :: :: ! 'out#'、'in#'的变量名应为不含引号的合规字符串,不能包含(&<|>^%!=,;)等特殊字符 :: ! 'in#'的变量名不能是'cd',因为该宏内部会使用'cd'变量 :: ! 除指定的'out#'变量外,外层环境不会被其他修改 :: 仅'*0'去嵌套模式除外,该模式会清空内部使用的'cd'变量 :: ! 宏执行后errorlevel会被重置为0,调用方若需要保留内层errorlevel值 :: 可通过'(%retLocals% ...) & set outErr=%errorlevel%'的方式捕获 set "retLocals=for %%# in (1 2) do if %%#==2 (%\n% setlocal enableDelayedExpansion%\n% set x=%\n% for %%L in "!LF!" do for %%R in "!CR!" do ^ for /f "tokens=1,*" %%U in "!cd!" do ^ for /f "tokens=2 delims=*" %%U in "%%U*%%U" do (%\n% set v=%%~V%\n% for /f "delims=" %%V in "!v:,=%%~L!" do ^ for /f "tokens=1,2 delims== " %%V in "%%~V=%%~V" do (%\n% set w=!%%~W!%\n% if defined w (%\n% set w=!w:^"=""q!%\n% set w=!w:%%~R=""r!%\n% set "path=" ^& set "pathExt=;"%\n% set "w=!w:^=^^^^!"%\n% call set "w=%%w:^!=^^^!%%"%\n% set "w=!w:^^=^!")%\n% if defined x set x=!x!!LF!%\n% set x=!x!"%%~V=!%%~W!"!LF!"%%~V=!w!")%\n% for /f "delims=" %%X in "!x!" do (%\n% if "!cd:~0,1!"=="*" (%\n% for /l %%U in (0 1 %%U) do endlocal%\n% set "cd=" ^& call;)%\n% if errorlevel 1 ((if "!"=="" (%\n% set "%%~X"!%\n% for /f "delims==" %%V in "%%~X" do (%\n% if defined %%~V (%\n% set %%~V=!%%~V:""r=%%~R!%\n% set %%~V=!%%~V:^""q="!%\n% )))) ^& call;%\n% ) else (if not "!"=="" set "%%~X") ^& call%\n% ))) else set cd=*1^" SETlocal EnableExtensions DisableDelayedExpansion SETlocal EnableExtensions DisableDelayedExpansion SETlocal EnableExtensions DisableDelayedExpansion SETlocal EnableExtensions DisableDelayedExpansion SETlocal EnableExtensions EnableDelayedExpansion ECHO ----------------------------- SET /a "ID2=2" %retLocals%*3 ID5=ID2 Echo This0: %ID2%,%ID5% EndLocal Echo This1: %ID2%,%ID5% EndLocal Echo This2: %ID2%,%ID5% EndLocal Echo This3: %ID2%,%ID5% ECHO ******************************* EndLocal Echo This4: %ID2%,%ID5% EndLocal Echo This5: %ID2%,%ID5% EndLocal EndLocal
内容的提问来源于stack exchange,提问作者hsgg4
相关产品推荐
相关产品推荐

