写在前面
Windows Batch 谈不上优雅,但它有一个很现实的优势:几乎每台 Windows 都能直接运行。启动程序、封装构建命令、复制文件、设置环境变量,一个 .bat 文件往往就够了。
本文不试图穷举 cmd.exe 的每个历史细节,而是建立一套能写出可维护 Batch 脚本的最小知识体系。
本系列共三篇:Windows Batch 负责 Windows 零依赖兼容,PowerShell 进入对象管道,Bash 面向 Linux 命令编排。
一、Batch、BAT 与 CMD
- Batch 是由 Windows 命令解释器按顺序执行的批处理脚本。
.bat / .cmd 是常见脚本扩展名,现代 Windows 中大多数场景下行为相同。cmd.exe 是解析和执行它们的程序。
一个最小脚本:
1
2
3
| @echo off
echo Hello, Batch!
pause
|
@ 只隐藏当前命令,echo off 隐藏后续命令本身,但不会隐藏命令的输出。
二、参数、变量与引号
2.1 命令行参数
1
2
3
4
| @echo off
echo script: %~f0
echo first: %~1
echo all: %*
|
%0 是脚本名,%1 到 %9 是位置参数,%* 是所有参数。%~1 会去掉外层引号,%~f0 会展开为脚本的绝对路径。
2.2 环境变量
1
2
3
| set "APP_ENV=production"
set "OUTPUT_DIR=%~dp0artifacts"
echo %APP_ENV%
|
推荐使用 set "NAME=value":引号不会进入变量值,又能避免行尾空格。%~dp0 表示脚本所在目录,适合构造与当前工作目录无关的路径。
路径可能含空格时,在使用处加引号:
1
2
| if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
copy "%~1" "%OUTPUT_DIR%\"
|
三、条件判断
1
2
3
4
5
6
7
8
9
10
11
| if "%~1"=="" (
echo Usage: %~nx0 ^<file^>
exit /b 2
)
if not exist "%~1" (
echo File not found: %~1
exit /b 3
)
if /i "%APP_ENV%"=="production" echo Production mode
|
数字比较使用 EQU、NEQ、LSS、LEQ、GTR 和 GEQ。
1
| if %COUNT% GTR 10 echo Too many items
|
if errorlevel N 的含义是“退出码大于等于 N”,不是“等于 N”。现代脚本通常直接保存 %ERRORLEVEL%:
1
2
3
| some-command
set "RC=%ERRORLEVEL%"
if not "%RC%"=="0" exit /b %RC%
|
四、循环与延迟展开
在交互式 CMD 中写 %F,在批处理文件中必须写 %%F:
1
2
3
4
5
| for %%F in ("*.log") do echo %%~nxF
for /d %%D in ("*") do echo directory: %%~fD
for /l %%N in (1,1,5) do echo %%N
|
for /f 用于读文件或命令输出:
1
| for /f "usebackq delims=" %%L in ("names.txt") do echo %%L
|
Batch 会在执行括号块前就展开 %VAR%。如果变量在块内变化,需要延迟展开:
1
2
3
4
5
6
7
8
| @echo off
setlocal EnableDelayedExpansion
set /a COUNT=0
for %%F in ("*.log") do (
set /a COUNT+=1
echo !COUNT!: %%~nxF
)
|
!VAR! 是执行时展开。但文本本身包含 ! 时可能被误解析,处理任意用户文本时要特别小心。
五、管道、重定向与转义
1
2
3
| tool.exe > output.log 2> error.log
tool.exe > all.log 2>&1
type app.log | findstr /i "error warning"
|
> 覆盖文件,>> 追加文件。2> 重定向标准错误。2>&1 让标准错误指向当前的标准输出。^ 是 CMD 的常见转义符。
1
2
| echo A ^& B
echo ^<value^>
|
六、子程序与作用域
1
2
3
4
5
6
7
8
9
10
| @echo off
setlocal
call :EnsureDirectory "%~dp0artifacts"
if errorlevel 1 exit /b %ERRORLEVEL%
exit /b 0
:EnsureDirectory
if not exist "%~1" mkdir "%~1"
if errorlevel 1 exit /b 1
exit /b 0
|
call :Label 调用子程序,exit /b 返回调用者。setlocal 将环境变量变更限制在当前脚本,遇到 endlocal 或脚本结束时恢复。
七、完整实战:封装构建命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| @echo off
setlocal EnableExtensions
set "ROOT=%~dp0"
set "PROJECT=%ROOT%src\Demo.csproj"
set "OUTPUT=%ROOT%artifacts"
where dotnet >nul 2>&1
if errorlevel 1 (
echo [ERROR] dotnet was not found in PATH.
exit /b 10
)
if not exist "%PROJECT%" (
echo [ERROR] Project not found: %PROJECT%
exit /b 11
)
if not exist "%OUTPUT%" mkdir "%OUTPUT%"
if errorlevel 1 exit /b 12
echo [INFO] Building %PROJECT%
dotnet publish "%PROJECT%" -c Release -o "%OUTPUT%"
set "RC=%ERRORLEVEL%"
if not "%RC%"=="0" (
echo [ERROR] Build failed with exit code %RC%.
exit /b %RC%
)
echo [INFO] Output: %OUTPUT%
exit /b 0
|
这个脚本有几个值得保留的习惯:路径基于 %~dp0,所有路径加引号,前置条件失败即返回,并把外部程序的退出码传递给调用者。
八、常见陷阱
- 不要写
set NAME = value,空格会成为变量名或值的一部分。 - 括号块内变化的变量需要延迟展开。
if errorlevel 1 是 >= 1,检查多个值时应从大到小。- 不要假设工作目录就是脚本目录。
- 用
exit /b 退出脚本;慎用 exit,它可能关闭整个 CMD 会话。 - Batch 对 JSON、复杂字符串和结构化数据很不友好,应及时转向 PowerShell 或其他语言。
九、什么时候使用 Batch
Batch 适合零依赖的 Windows 启动器、简单命令编排和兼容历史环境。如果脚本开始大量解析文本、访问 API、操作 JSON 或维护复杂错误分支,PowerShell 通常是 Windows 上更好的选择。
下一篇:PowerShell 语法与对象管道
参考资料