cmd.exe 照開檔當下的字碼頁逐行讀批次檔,UTF-8 中文會讓行解析錯位,切出來的碎片還會被當成指令執行。
發布於 2026-09-05・約 3 分鐘
我寫了一支新電腦設定工具,入口是一個雙擊就跑的 .bat,裡面畫一個中文選單:
.bat
echo [1] 設定 echo [2] 執行 echo [5] 驗證
存成 UTF-8,雙擊,畫面上吐出一堆「不是內部或外部命令」,而且它抱怨的那些「指令」我從來沒打過,長得像中文被剁掉一半的碎片。
cmd.exe 不是把整個批次檔讀進記憶體再執行的。它是執行一行、記住檔案位置、回頭再讀下一行,而解碼用的是「開啟這個檔案當下的字碼頁」。
繁體中文 Windows 預設是 950 (Big5)。UTF-8 的中文一個字佔三個位元組,用 950 去解就會得到完全不同的字元數,於是 cmd 記下來的檔案位置跟它以為的位置對不上。錯位之後,下一行的起點會落在某個中文字的中間,前半段被當成指令名稱送去執行。
錯位是會累積的。所以症狀通常是「前面幾行看起來正常,愈後面愈亂」,很容易誤判成是後面那幾行本身寫錯。
在檔案開頭加 chcp 65001 沒有用,我試過。字碼頁換掉了,但檔案已經開著,解析器的位置基準是開檔那一刻定下來的。
chcp 65001
這是實測到的行為,微軟沒有文件保證每個版本都一樣,但從 Windows 10 到 11 我遇到的都是這樣。
批次檔只放 ASCII,一個中文字都不留,它只負責兩件事:UAC 提權、把參數轉發給 PowerShell。看得到的中文介面全部畫在 .ps1 裡。
.ps1
@echo off REM IMPORTANT: keep this file ASCII-only. REM cmd.exe parses a batch file line by line using the code page that was REM active when the file was opened. UTF-8 multi-byte characters desync that REM parser: lines get split in the middle and cmd tries to execute the pieces. setlocal enableextensions set "ROOT_DIR=%~dp0"
PowerShell 讀 .ps1 是照 BOM 或 UTF-8 判斷的,沒有這個問題,中文放多少都不會壞。
提權的參數不要內嵌進字串。 這是我原本的寫法:
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -ArgumentList '%1' -Verb RunAs"
只要路徑或參數裡出現一個單引號(D:\Alice's PC\... 這種資料夾名稱是存在的),字串就從那裡被切斷,後面的內容變成 PowerShell 指令。改成用環境變數傳:
D:\Alice's PC\...
set "AS_SELF=%~f0" set "AS_MODE=%MODE%" powershell -NoProfile -Command ^ "$a = @($env:AS_MODE) | Where-Object { $_ }; if ($a.Count -gt 0) { Start-Process -FilePath $env:AS_SELF -ArgumentList $a -Verb RunAs } else { Start-Process -FilePath $env:AS_SELF -Verb RunAs }"
環境變數的內容不會被 PowerShell 的解析器再看一次,引號、空白、驚嘆號都安全。
判斷有沒有管理員權限,用 net session:
net session
net session >nul 2>&1 if %errorlevel% EQU 0 goto :HaveAdmin
它需要管理員權限才會成功,不需要就回非零,比去讀權限 API 短很多,而且在所有版本上行為一致。
連續的 if 不要串成 else-if。 cmd 對「else-if 串接加上子字串展開」的解析很脆弱,同一段邏輯寫成鏈狀就會出錯。改成從預設值開始、用平行的 if 一層層覆蓋:
if
set "CONFIG_FILE=%ROOT_DIR%Config\AutoSet.config.json" if not "%CONFIG_ARG%"=="" set "CONFIG_FILE=%ROOT_DIR%Config\%CONFIG_ARG%" if not "%CONFIG_ARG%"=="" if exist "%CONFIG_ARG%" set "CONFIG_FILE=%CONFIG_ARG%"
行數多了兩行,但每一行都只做一件事,讀的人和 cmd 都不會誤會。
如果你的批次檔出現「不是內部或外部命令」,而且它抱怨的指令名稱你完全沒印象,先看檔案編碼,不要先看那一行寫了什麼。用記事本另存成 ANSI 再跑一次,如果錯誤消失,那就是編碼問題,不是邏輯問題。
長遠的解法還是把中文搬出批次檔。ANSI 存檔可以讓中文顯示正常,但那份檔案就綁死在特定字碼頁的機器上了,換一台語系不同的電腦又會壞。
標籤:Windows、批次檔、cmd、PowerShell、編碼