命令行脚本实战(一):Windows Batch(BAT)语法与自动化

写在前面

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

数字比较使用 EQUNEQLSSLEQGTRGEQ

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,所有路径加引号,前置条件失败即返回,并把外部程序的退出码传递给调用者。


八、常见陷阱

  1. 不要写 set NAME = value,空格会成为变量名或值的一部分。
  2. 括号块内变化的变量需要延迟展开。
  3. if errorlevel 1>= 1,检查多个值时应从大到小。
  4. 不要假设工作目录就是脚本目录。
  5. exit /b 退出脚本;慎用 exit,它可能关闭整个 CMD 会话。
  6. Batch 对 JSON、复杂字符串和结构化数据很不友好,应及时转向 PowerShell 或其他语言。

九、什么时候使用 Batch

Batch 适合零依赖的 Windows 启动器、简单命令编排和兼容历史环境。如果脚本开始大量解析文本、访问 API、操作 JSON 或维护复杂错误分支,PowerShell 通常是 Windows 上更好的选择。

下一篇:PowerShell 语法与对象管道

参考资料