批处理For循环中输出PowerShell代码时的转义编码问题排查与解决
批处理For循环中输出PowerShell代码时的转义编码问题排查与解决
问题背景
最近我碰到个挺闹心的问题:把一段调用PowerShell做数值计算的批处理代码,整合到遍历文件的For循环里之后,代码突然卡住报错了。错误提示是[0] was unexpected at this time.,正好卡在输出PowerShell脚本内容的那一行。
我一开始试着用延迟扩展的!替换%来引用变量,比如把%MathType%改成!MathType!,但还是没解决问题,折腾了好一阵才找到关键所在。
问题根源
在批处理的For循环环境下,尤其是启用了延迟扩展后,[]这类符号会被批处理解释器当成自身的语法元素解析,而不是当作普通文本输出。当我们要把[int]$Calculation.split("...")[0]这类PowerShell代码写入文件时,批处理会错误地把[0]识别成自己的命令语法,从而触发报错。
解决方案
核心就是对]进行转义——在批处理里,用^符号来转义特殊字符。我们只需要在每个要输出的]前面加上^,就能让批处理把它当成普通文本处理,正确写入到PowerShell脚本文件中。
修改前的错误代码行:
ECHO [int]$Calculation.split("!MathType!")[0] !MathType! [int]$Calculation.split("!MathType!")[1] >> "!PSScript!"
修改后的正确代码行:
ECHO [int]$Calculation.split("!MathType!"^)[0] !MathType! [int]$Calculation.split("!MathType!"^)[1] >> "!PSScript!"
完整可运行脚本
这里附上我最终调试通过的完整脚本,它可以遍历指定文件列表里的视频文件,用ffmpeg转码压缩,还会自动计算压缩比率:
REM Change the console encoding to UTF-8 (for Nordic characters). chcp 65001 | echo/ set filelist=%1 echo %filelist% REM Define ESC escape character for /f %%d in ('echo prompt $E ^| cmd') do (set "ESC=%%d") REM Loop through all video files lists in a separate filelist for /f "usebackq tokens=*" %%a in ("%filelist%") do ( call :StartTimer set Bytes=%%~za REM Below timestamp coding depends on locale setting on computer set Timestamp=%%~ta set TSYYYY=!Timestamp:~6,4! set TSYY=!Timestamp:~8,2! set TSMM=!Timestamp:~3,2! set TSDD=!Timestamp:~0,2! set TSHR=!Timestamp:~11,2! set TSMIN=!Timestamp:~14,2! set TSDato=!TSYY!!TSMM!!TSDD! set TSTid=!TSHR!!TSMIN! REM Display information about input file echo !ESC![92;103mInputfile="%%~fa"!ESC![0m echo !ESC![92;103mInputfile size=!Bytes! Bytes!ESC![0m REM Convert to MP4 if needed, and compress to iPhone supported HEVC format echo !ESC![91mConvert %%~xa file to MP4 format, and compress to iPhone supported HEVC format!ESC![0m ffmpeg -n -hide_banner -loglevel quiet -i "%%~fa" -vcodec libx265 -vsync 0 -vtag hvc1 -crf 28 "%%~dpna_!TSDato!-!TSTid!.mp4" REM Reset Console command after running ffmpeg chcp 65001 | echo/ REM Find size of new file FOR /F "tokens=*" %%b IN ("%%~dpna_!TSDato!-!TSTid!.mp4") DO set OutBytes=%%~zb REM Find compression ratio new vs old file SET "Calculation=!OutBytes!/!Bytes!" SET "MathType=/" SET PSScript=%temp%\PS~MathTemp.ps1 IF EXIST "!PSScript!" DEL /Q /F "!PSScript!" ECHO $Calculation = "!Calculation!" > "!PSScript!" ECHO [int]$Calculation.split("!MathType!"^)[0] !MathType! [int]$Calculation.split("!MathType!"^)[1] >> "!PSScript!" FOR /F "USEBACKQ DELIMS=" %%c IN (`Powershell -ExecutionPolicy Bypass -Command "& '!PSScript!'"`) DO SET "Compression=%%c" REM Display compression summary echo !ESC
