Windows 命令提示符(CMD)批处理脚本基础

大家好,你们可以叫我凌,是个16岁的网络安全学习者。

我们今天来讲讲Windows的命令提示符当中的批处理脚本,废话不多说我们直接开始吧!


你的首个 .bat 文件

.bat 文件介绍

什么是 .bat 文件

  • .bat 是 批处理文件 的扩展名,它是个纯文本文件。

  • 里面写的每一行,都是你平时在 CMD 窗口中手动敲过的命令。

  • 当你双击或调用这个文件时,cmd.exe 会从上到下、逐行执行里面的命令。

为什么它算"程序"

因为它具备了程序最基本的能力:

  • 按顺序做事(顺序执行)

  • 根据条件做不同的事(条件判断,后面学 if)

  • 反复做同一件事(循环,后面学 for 和 goto)

你现在写的每行脚本,都是在让计算机自动完成一系列操作,这正是自动化运维的起点。

创建你的首个 .bat 文件

第1步:新建文本文件

在桌面上或任意文件夹里,右键 → 新建 → 文本文档。

第2步:写入内容

复制下面这段代码(特别注意:保存时编码选 ANSI,否则中文乱码)

bash 复制代码
@echo off
title 我的第一个批处理
color 0A
cls
echo ==============================
echo     欢迎来到批处理的世界
echo ==============================
echo.
echo 当前系统时间:%time%
echo 当前系统日期:%date%
echo.
echo 按任意键退出...
pause > nul

以下为以CMD命令做的演示,供参考:

第3步:重命名

把 text.txt 改名为 text.bat(如果看不到 .txt,需要勾选"显示文件扩展名")。

以下以命令提示符的方式进行演示,供参考:

bash 复制代码
ren text.txt text.bat

第4步:运行

双击目标文件或使用CMD输入文件名并回车,即可查看效果。

bash 复制代码
text.bat

运行后如下所示

程序命令分析

|------------------|-----------|--------------------------------------------------------------------------------------|
| 命令 | 作用 | 细节 |
| @echo off | 关闭命令回显 | 默认 CMD 会先把"命令原文"打印出来再执行结果,看着很乱。echo off 关掉这个行为,前面的 @ 让 echo off 本身也不显示。这是所有批处理的标准开头。 |
| title XXX | 修改窗口标题栏文字 | 运行后窗口左上角显示你指定的标题,不再是"命令提示符" |
| color 0A | 设置窗口颜色 | 第一位是背景色(0=黑),第二位是前景色(A=亮绿)。常用:0A 黑绿、0C 黑红、1E 蓝黄。 |
| cls | 清屏 | 把窗口里之前残留的内容清干净,给你个清爽的屏幕。 |
| echo ===== | 输出文字 | 把后面的内容原样打印到屏幕上。 |
| echo. | 输出空行 | echo 后面跟一个点(或冒号),会打印一个空白行,用来让排版更美观。 |
| echo 当前时间:%time% | 显示系统变量 | %time% 和 %date% 是系统自带的动态环境变量,会实时取出当前时间日期。 |
| pause > nul | 暂停执行 | 显示"按任意键继续...",脚本停在这里等用户按键。> nul 把提示文字藏起来,只保留停顿效果。 |
| rem 或 :: | 添加注释 | 在脚本里写说明文字,不会被执行。例如"rem 这是注释" 或 ":: 这也是注释"。 |

.bat 的执行方式

|-------------|------------------------|----------------------|------------------------|
| 方式 | 操作 | 特点 | 适用场景 |
| 双击 | 鼠标双击 .bat | 执行完自动关闭窗口(除非有 pause) | 最终用户使用 |
| 管理员身份运行 | 右键 → 以管理员身份运行 | 获得最高权限,可以修改系统目录和注册表 | 需要高权限的操作(如改 hosts、装服务) |
| 在 CMD 中手动调用 | 打开 CMD,输入 First.bat 回车 | 窗口不关闭,能看到所有输出和报错 | 开发调试阶段永远用这个 |

注意:写批处理时,建议先用 CMD 手动调用测试,完全没问题了再双击。如果直接双击而脚本又报错,窗口一闪而过,你根本看不到错误信息。

变量:让脚本拥有"记忆"

什么是变量?为什么需要它?

之前写 .bat,所有命令都是写死的。比如:

bash 复制代码
echo 当前时间是:%time%

这里的 %time% 就是系统帮我们定义好的变量,它会动态变化。

变量的本质:在内存中开辟一个小格子,给它起个名字,存进去一个值,后面随时可以取出来用。

没有变量:你要处理 5 个文件名,就得手动改 5 次脚本。

有了变量:你只需改一次变量的值,脚本自动适应。

set 命令:定义、修改变量

基本的用法:设置一个变量

在 CMD 中(或 .bat 里)输入:

bash 复制代码
set name=张三
echo %name%

运行结果:张三

如图所示:

拆解:

  • set 是命令关键字。

  • name 是变量名(你可以随便起,但建议见名知义)。

  • = 是赋值符号。

  • 张三 是存入的值。

注意:等号两边不要有空格!写 "set name = 张三" 是错的,会导致变量名变成 "name "(带空格)。

当然,依照我们前面学习过的。也可以关闭回显使界面更加美观:

bash 复制代码
@echo off
set name=张三
echo %name%

查看已有的变量

  • 在 CMD 中直接输入 set 回车,会列出当前会话中所有的环境变量(非常多)。

  • 想看特定的某个变量:set name,会显示所有以 name 开头的变量。

如图所示:

删除变量

bash 复制代码
set name=

把变量赋值为空,就是删除它。之后再 echo %name% 只会显示 %name% 原文(因为变量不存在了)。

变量值的特殊性

在批处理中,所有变量默认都是字符串。哪怕你存的是数字,它也是文本。

bash 复制代码
set num=123
echo %num%

输出是 123,但它本质是字符串 "123",不能直接做数学运算(后面会讲 set /a 来做运算)。

引用变量:%变量名%

引用变量的固定写法是:变量名前后各加个 "%"。

bash 复制代码
set myfile=test.txt
echo 我要操作的文件是:%myfile%
copy %myfile% backup_%myfile%

第这样子第三行会变成:copy test.txt backup_test.txt。

记住:

  • 设置变量用 set x=值(不加 %)

  • 读取变量用 %x%(加 %)

交互式输入:set /p

这是非常有用的功能:脚本运行到一半,停下来问用户要信息。

语法:

bash 复制代码
set /p 变量名=提示文字

示例:

bash 复制代码
@echo off
set /p name=请输入你的名字:
echo 你好,%name%!
pause

运行效果:

请输入你的名字:张三

你好,张三!

/p 的含义:prompt(提示),等待用户输入并按下回车,把输入的内容存进变量。

如图所示:

数学运算:set /a

因为默认是字符串,所以要做加减乘除,必须加 /a 参数。

示例:

bash 复制代码
@echo off
set /a num1=10
set /a num2=3

set /a result=num1 + num2
echo 10 + 3 = %result%

set /a result=num1 * num2
echo 10 * 3 = %result%

pause

输出:

10 + 3 = 13

10 * 3 = 30

支持的运算符:+(加)、-(减)、*(乘)、/(除)、%(取余)

注意:在批处理中取余要写成 "%%",在CMD中直接 set /a 10%%3 会报错,以下为解释供参考

1.在命令行窗口:取模运算使用单个 %,例如 set /a 10%3 就能正确计算 10 除以 3 的余数。

2.在批处理文件中:需要对 % 进行转义,使用两个连续的 %,即 %%。例如,在 .bat 脚本中要写 set /a num=4%%2。

% 在 CMD 里用来引用变量(如 %var%)。当你输入 10%%3 时:

CMD 优先寻找变量:它把第一个 % 和第二个 % 配对,试图解析它们之间的内容(这里是空或数字)作为一个变量名。

变量不存在:由于没有名为 10 或类似名称的变量,解析失败。

报错:最终,set /a 命令无法识别这个表达式,因此报错"缺少运算符"。

变量生效的时机

批处理在执行代码前,会预先将所有的 %变量% 替换成当时的值,然后再执行命令。这叫预处理。

请看下面这个脚本:

bash 复制代码
@echo off
set num=1
if %num%==1 (
    set num=2
    echo 现在的num是:%num%
)
pause

大部分人的预期是输出 "现在的num是:2",但实际输出是 "现在的num是:1"。

原理讲解

因为 if 语句后面的括号 ( ) 里的内容,在被执行之前 %num% 就已经被替换成了最开始的 1。里面那个 "set num=2" 虽然确实修改了内存里的值,但 echo 语句里的 %num% 早在修改之前就"翻译"好了,不会再变。

解决方法

在脚本开头加上一行 "setlocal enabledelayedexpansion",然后引用变量时用 "!变量名!" 代替 "%变量名%"。

修正后的正确写法:

bash 复制代码
@echo off
setlocal enabledelayedexpansion
set num=1
if %num%==1 (
    set num=2
    echo 现在的num是:!num!
)
pause

此时输出正确:现在的num是:2

> - 绝大多数情况下,用 %var% 就够。

> - 只有在 if 或 for 的括号 ( ) 内部修改变量并立即读取时,才需要加 setlocal enabledelayedexpansion 并用 !var!。

特殊变量:%~dp0 与相关变量

这是批处理中最常用、最实用的特殊变量,它的作用是:获取当前 .bat 脚本所在的文件夹路径。

%0 是什么

%0 代表脚本自身的完整路径和文件名。比如你的脚本放在 C:\test\my.bat,那么在脚本内部,%0 的值就是 C:\test\my.bat。

各种 %~ 扩展

在 %0 的基础上,加不同的符号可以提取路径的不同部分:

|--------|---------------------|----------------------------|
| 写法 | 含义 | 示例(假设脚本在 C:\test\my.bat) |
| %0 | 脚本自身的完整路径+文件名 | C:\test\my.bat |
| %~d0 | 只取盘符(d=drive) | C: |
| %~p0 | 只取路径(p=path,不含盘符) | \test\ |
| %~n0 | 只取文件名(n=name,不含扩展名) | my |
| %~x0 | 只取扩展名(x=extension) | .bat |
| %~dp0 | 盘符 + 路径(d+p) | C:\test\ |
| %~f0 | 完整的绝对路径(f=full) | 同 %0,但会规范化路径 |

为什么 %~dp0 如此重要

写脚本时,经常需要引用跟脚本放在同个文件夹里的其他文件。

错误的写法

bash 复制代码
copy config.ini backup\

如果你在 C:\test 下双击脚本能正常找到 config.ini。但如果你在 C:\windows 下用绝对路径调用这个脚本(比如 C:\test\my.bat),那么当前工作目录是 C:\windows,脚本会去 C:\windows\config.ini 当中找,结果是根本找不到。

正确的写法

bash 复制代码
copy "%~dp0config.ini" "%~dp0backup\"

这样无论你在哪里执行这个 .bat,它都会老老实实去找 自己所在文件夹 里的 "config.ini"

流程控制:if 和 goto

if 命令:让脚本做判断

if 的作用是:条件成立,就执行后面的命令。

字符串/数字比较:if 和 ==

最基本的用法是比较两个值是否相等。

语法:

bash 复制代码
if 值1 == 值2 命令

示例:

bash 复制代码
@echo off
set /p input=请输入 y 或 n:
if %input% == y echo 你输入了 y
if %input% == n echo 你输入了 n
pause

如图所示:

注意:

  • == 两边要有空格(固定写法)。

  • 如果变量可能为空,或者包含空格,一定要加双引号,否则会报错。

安全写法(强烈推荐):

bash 复制代码
if "%input%" == "y" echo 你输入了 y

数值比较

对于纯数字的比较,除了 ==,更专业的做法是用这些运算符(英文缩写):

|-----|-----------------|-----------|
| 运算符 | 含义 | 类比 C/JAVA |
| EQU | 等于 (Equal) | == |
| NEQ | 不等于 (Not Equal) | !=LSS |
| LSS | 小于 (Less) | < |
| LEQ | 小于等于 | <= |
| GTR | 大于 (Greater) | > |
| GEQ | 大于等于 | >= |

示例:

bash 复制代码
@echo off
set /a score=85
if %score% GEQ 60 echo 及格了!
if %score% LSS 60 echo 不及格,继续努力。
pause

忽略大小写比较:/i 参数

用户可能输入大写 Y,也可能输入小写 y。加上 /i 就不区分大小写了。

bash 复制代码
if /i "%input%" == "y" echo 你输入了 y(不分大小写)

检查文件/文件夹是否存在:if exist

这是运维和脚本中最常用的功能------先检查再操作,避免报错

语法:

bash 复制代码
if exist 文件路径 命令

示例:判断 C:\test.txt 是否存在,存在就删除它。

bash 复制代码
@echo off
if exist C:\test.txt (
    echo 找到文件,正在删除...
    del C:\test.txt
) else (
    echo 文件不存在,跳过删除。
)
pause

如图所示(改进版):

else 子句

if 可以搭配 else 表示"否则"。

语法:

bash 复制代码
if 条件 (
    命令1
) else (
    命令2
)

关键规则:

  • ) 和 else 必须写在同一行。

  • else 和 ( 必须写在同一行。

错误的写法(会报错)

bash 复制代码
if 条件 (
    命令1
)
else (
    命令2
)

这种写法 CMD 会把 if 在 ) 处结束,然后单独遇到 else,直接报 'else' 不是内部或外部命令。

带变量的复杂判断示例

bash 复制代码
@echo off
setlocal enabledelayedexpansion
set /p num=请输入一个数字:
if !num! GTR 10 (
    echo 你输入的数字大于 10
) else (
    echo 你输入的数字小于或等于 10
)
pause

goto 命令:让脚本"跳起来"

goto 的作用是:让脚本的执行流程,直接跳转到脚本中指定的"标签"位置

标签的定义:":" + 标签名

在脚本的任意位置,另起一行,用冒号加名字定义一个标签。

bash 复制代码
:start
echo 这里是开始

goto 的使用:无条件跳转

bash 复制代码
@echo off
echo 第一步
goto skip_part
echo 第二步(这行永远不会被执行)
:skip_part
echo 第三步(跳到了这里)
pause

运行结果:

bash 复制代码
第一步
第三步(跳到了这里)

中间的"第二步"被完美跳过。

死循环与逻辑结束

如果不加限制地 goto,脚本会跑飞。通常配合 if 使用。

也可以用 goto :eof 直接跳到文件末尾(结束脚本)。eof 是 End Of File 的缩写,是系统内置的特殊标签。

这里就不进行演示了,感兴趣的可私下研究。


综合实验

实验一:带有"记忆"和"容错"的智能计算器

目标:编写 Calculator.bat,实现一个无限循环的计算器菜单,直到用户选择退出。

代码示例:

bash 复制代码
@echo off
title 计算器
setlocal enabledelayedexpansion

:menu
cls
echo ====== 计算器 ======
echo 1. 加法
echo 2. 减法
echo 3. 乘法
echo 4. 除法
echo 5. 退出
echo ====================
set /p choice=请输入选项 (1-5):

if "%choice%"=="1" goto add
if "%choice%"=="2" goto sub
if "%choice%"=="3" goto mul
if "%choice%"=="4" goto div
if "%choice%"=="5" goto exit

echo 无效选项,请重新输入!
pause
goto menu

:add
set /p num1=请输入第一个数字:
set /p num2=请输入第二个数字:
set /a result=num1+num2
echo 结果:%num1% + %num2% = %result%
pause
goto menu

:sub
set /p num1=请输入第一个数字:
set /p num2=请输入第二个数字:
set /a result=num1-num2
echo 结果:%num1% - %num2% = %result%
pause
goto menu

:mul
set /p num1=请输入第一个数字:
set /p num2=请输入第二个数字:
set /a result=num1*num2
echo 结果:%num1% * %num2% = %result%
pause
goto menu

:div
set /p num1=请输入被除数:
set /p num2=请输入除数:
if "%num2%"=="0" (
    echo 除数不能为 0!
    pause
    goto menu
)
set /a result=num1/num2
echo 结果:%num1% / %num2% = %result%
pause
goto menu

:exit
echo 感谢使用,再见!
pause
exit

实验二:手工打造"计数器"实现批量创建文件夹

目标:编写 BatchCreator.bat,让用户输入一个数字(比如 5),然后自动创建 文件夹1、文件夹2 ... 一直到 文件夹5。

操作逻辑:

用 set /p 让用户输入要创建的数量(存入 total)。

定义计数器变量:set /a count=1。

定义一个标签 :loop。

在里面执行 md "文件夹!count!"(注意用 !count! 实时取值)。

执行 set /a count=count+1(计数器加 1)。

用 if !count! LEQ !total! goto loop(如果没达到总数,就跳回循环)。

最后输出"全部创建完成!"。

代码示例:

bash 复制代码
@echo off
setlocal enabledelayedexpansion

:: 1. 让用户输入要创建的数量
set /p total=请输入要创建文件夹的数量:

:: 2. 定义计数器变量,初始为 1
set /a count=1

:: 3. 定义循环标签
:loop

:: 4. 判断当前计数是否小于等于总数
if !count! LEQ !total! (
    :: 创建文件夹,使用 !count! 实时取值
    md "文件夹!count!"
    
    :: 计数器加 1
    set /a count=count+1
    
    :: 跳回循环继续判断
    goto loop
)

:: 5. 循环结束,输出完成信息
echo 全部创建完成!
pause

实验三:脚本自检与配置文件生成器

目标:编写 CheckEnv.bat,实现一个"环境检测与修复"小工具。

要求:

  1. 脚本启动后,首先检查 脚本自己所在的目录 下,是否存在 config.ini 文件。

  2. 如果 不存在 config.ini,则自动创建一个。内容如下

System

Status=Ready

CreateTime=%date% %time%

代码示例:

bash 复制代码
@echo off
setlocal

:: 获取脚本所在目录下的 config.ini 完整路径
set "configfile=%~dp0config.ini"

:: 检查文件是否存在
if exist "%configfile%" (
    :: 存在:显示内容并提示
    echo 配置文件已加载
    echo ------------------------------
    type "%configfile%"
    echo ------------------------------
) else (
    :: 不存在:创建文件并写入内容
    echo 配置文件缺失,已自动重建!
    (
        echo [System]
        echo Status=Ready
        echo CreateTime=%date% %time%
    ) > "%configfile%"
)

:: 暂停以便查看结果
pause