批处理脚本中set str1=#%str1%的含义及含<>时误覆写文件的原因
批处理脚本处理含特殊字符日志时的文件操作问题解决
问题说明
写了个批处理脚本,用来逐行读取日志文件,提取错误行的LineNo(行号)和ErrorNo(错误号),生成Visual Studio输出窗口兼容的格式,方便直接跳转错误代码。但遇到日志行里带>或<这类特殊字符时,脚本会意外执行文件操作:比如示例1会创建名为(parameter的文件,示例2会覆写abc.xyz文件。注释掉set str2=#%str1%能停止文件操作,但会导致无法正确识别LineNo和ErrorNo。
脚本调用方式:MyBat.bat build.log,需要从&rem.之前的子串获取错误代码前的正确索引(错误1对应17,错误2对应16),同时避免触发文件覆写。
原问题脚本
@echo off for /F "tokens=* delims=" %%A in (Build.log) do call :Process "%%A" %1 goto :eof :Process setlocal enableDelayedExpansion call :GetLineError %1 LineNo ErrorNo echo."%2(!LineNo!):error(!ErrorNo!):" %1 goto :eof :GetLineError setlocal enableDelayedExpansion set string="%~1" rem set "string=%string:<=#%" rem set "string=%string:>=#%" set "find_index_of=error code:" call :indexof %string% "%find_index_of%" index :indexof [%1 - string ; %2 - find index of ; %3 - if defined will store the result in variable with same name] setlocal enableDelayedExpansion set "str=%~1" set "str1=!str:%~2=&rem.!" REM 这行是问题根源 set str2=#%str1% call :strLen str2 strlen rem echo.String is %strlen% characters long (endlocal & set "%3=%strlen%") exit /b :strLen setlocal enabledelayedexpansion :strLen_Loop if not "!%1:~%len%!"=="" set /A len+=1 & goto :strLen_Loop (endlocal & set %2=%len%) goto :eof
原问题输出
mytool: line 63, mytool: line 63, &rem. 541 , can not omit value. ===> (parameter no 1) line 63, #mytool: line 63, #mytool: line 63, line 63, 74 mytool: line 0, mytool: line 0, &rem. 5 , file is abnormal. ===> C:\Projects\abc_DemoSampleExample\abc\my\abc.xyz line 0, #mytool: line 0, #mytool: line 0, line 0, 122
问题根源
问题出在set str2=#%str1%这行:这里用百分号%展开变量str1,当str1里包含>、<、&这类特殊字符时,会被CMD解析成命令执行。比如日志里的> (parameter no 1)会被当成重定向命令,从而创建新文件;> C:\...\abc.xyz会直接覆写目标文件。
修复方案
核心思路是全程使用延迟扩展(!变量!)替代百分号展开,避免CMD将特殊字符解析为命令:
- 将
set str2=#%str1%改为set "str2=#!str1!",用延迟扩展展开str1,同时用引号包裹赋值语句防止特殊字符逃逸。 - 调整
:indexof函数里的变量处理逻辑,确保所有字符串操作都用延迟扩展。 - 移除对特殊字符的临时替换(注释掉的
set "string=%string:<=#%"),因为延迟扩展已经能处理这些字符。
修复后的完整脚本
@echo off for /F "tokens=* delims=" %%A in (Build.log) do call :Process "%%A" %1 goto :eof :Process setlocal enableDelayedExpansion call :GetLineError %1 LineNo ErrorNo echo."%2(!LineNo!):error(!ErrorNo!):" %1 endlocal goto :eof :GetLineError setlocal enableDelayedExpansion set "string=%~1" set "find_index_of=error code:" call :indexof "!string!" "%find_index_of%" index REM 提取行号和错误号的逻辑 set "linePart=!string:*line =!" for /F "tokens=1 delims=," %%L in ("!linePart!") do set "LineNo=%%L" set "errorPart=!string:*error code: =!" for /F "tokens=1 delims=," %%E in ("!errorPart!") do set "ErrorNo=%%E" endlocal & set "%2=%LineNo%" & set "%3=%ErrorNo%" exit /b :indexof [%1 - 目标字符串; %2 - 要查找的子串; %3 - 存储结果的变量名] setlocal enableDelayedExpansion set "str=%~1" set "subStr=%~2" set "str1=!str:%subStr%=&rem.!" set "str2=#!str1!" call :strLen str2 strlen set /A "index=strlen-1" endlocal & set "%3=%index%" exit /b :strLen setlocal enabledelayedexpansion set "len=0" :strLen_Loop if not "!%1:~%len%!"=="" set /A len+=1 & goto :strLen_Loop endlocal & set %2=%len% goto :eof
修复效果
- 不再触发任何意外的文件创建/覆写操作。
- 能正确计算
error code:之前的子串长度(错误1得到17,错误2得到16)。 - 生成的输出格式兼容Visual Studio,可直接跳转错误行。
内容的提问来源于stack exchange,提问作者Peeyush
相关产品推荐
相关产品推荐

