PowerShell 和 CMD 是 Windows 上绕不开的两个终端,尤其当你开始接触 Git、conda、Docker、Python 这类工具时,几乎每天都要和命令行打交道。但很多新手第一次打开“终端”时都会困惑:为什么网上教程里写的是 CMD,自己电脑默认打开却是 PowerShell?为什么同一条指令在 CMD 里能跑,粘到 PowerShell 里就报错?甚至有人以为是系统坏了,重装完发现还是一样。这类问题的根源并不是命令本身错了,而是你没搞清楚当前“这个终端到底是谁”。这篇文章就把 PowerShell 与 CMD 常用指令之间的差异完整梳理一遍,重点放避坑:路径、参数、脚本、执行策略、常见报错,哪一步容易翻车就拆哪一步。适合刚开始接触命令行、第一次写 .bat 或 .ps1、想把 Windows 自动化脚本跑起来的同学参考。
我尽量少讲虚的,所有案例都来自日常操作里能直接复现的场景。你不需要全部背下来,把这篇文章当作查表工具用就行,遇到问题再回来翻对应章节。
1. CMD 和 PowerShell 的区别,别只看表面
1.1 CMD:从 DOS 时代传下来的“文本处理管线”
CMD 即 cmd.exe,是 Windows 自带的命令解释器,直接继承了 DOS 时代的交互方式和批处理习惯。它最显著的特征就是:启动快、轻量、到处都有。不管多老的 Windows 版本都能打开,.bat 和 .cmd 脚本至今仍被软件安装包、系统运维脚本大量使用,很多部署工具在 Windows 上默认调用的还是 CMD。
但 CMD 的底层模型是文本。它看到的世界就是一串字符流:执行一条命令,得到一段格式化文本;想从结果里取某个字段,就得用 findstr、for /f 这类“文本加工厂”去二次解析。比如你想知道 8080 端口被哪个进程占用了,CMD 的做法是给你打印一整张表,然后你自己人眼去找,或者用 findstr 过滤出包含 8080 的那一行,再对着表头逐列数第几个数字是 PID。这种思路在几十年前没问题,但放到今天的自动化场景里确实很吃力。
CMD 的另一个特点是脚本语法简陋。变量用 %var% 包裹,if/for 的写法极其反直觉,循环里还不能直接用管道处理复杂的对象数据。写个稍微复杂点的批处理,基本就是在和字符串转义、行尾空格、编码乱码作斗争。对新手来说,CMD 的入门门槛低,但一旦需求超过“敲两行命令查信息”的范围,就会迅速变得折磨人。
1.2 PowerShell:以对象为单位的现代命令行环境
PowerShell 是微软基于 .NET 重新设计的命令行环境和脚本语言,它的命令叫 cmdlet,命名规则是“动词-名词”,比如 Get-Process、Get-Service、Set-ExecutionPolicy。PowerShell 最大的变化不是多了几百个命令,而是管道的传输单位变了:管道里不再流动文本,而是流动对象。
这句话怎么理解?以列目录为例。CMD 的 dir 输出是一段排版好的文本,文件名、大小、日期等信息被格式化成了人眼看起来整齐的表格,但后续程序想再拿这些数据去做处理,就得靠文本切割,非常脆弱。而 PowerShell 的 Get-ChildItem 会把每个文件都变成一个对象,每个对象自带 Name、FullName、Length、LastWriteTime 等属性。你可以把结果直接传给 Where-Object 做筛选、传给 Sort-Object 做排序、传给 Select-Object 挑自己关注的列,最后输出只是一层“展示壳”,底层数据全程都是结构化的。
也正因为对象化,PowerShell 的语法复杂度明显比 CMD 高。变量、管道、作用域、错误处理、模块导入、远程管理……学习曲线会陡一些。但换来的是脚本能力的质变,尤其是批量处理文件、操作注册表、管理系统服务、对接 .NET 对象和各类 API 的时候,PowerShell 要顺手太多。
还有一个新手容易混淆的点:Windows 系统自带的是 Windows PowerShell 5.1,它基于 .NET Framework,是系统内建组件,不能随便卸载。微软后来推出的 PowerShell 7 是独立安装的跨平台版本,基于 .NET。两者都叫 PowerShell,但默认环境里命令集合和兼容性有差异。你搜“powershell 5.1 下载”时,看到的通常是把系统自带组件和独立发行版搞混了。
1.3 两个终端该怎么选,其实不用纠结
很多教程喜欢帮人分阵营,我个人的看法是:
- 只想敲一两条命令查信息、跑一个 exe,两个都行,哪个顺手用哪个。
- 工作内容依赖 .bat/.cmd 脚本,就继续留在 CMD,或者从 PowerShell 里显式调用
cmd /c xxx.bat。 - 要写自动化脚本、做系统配置、批量处理文件、操作服务进程,优先选 PowerShell。
- 使用第三方命令行工具时,工具文档说“在终端执行”却没说清哪种,优先在 PowerShell 里尝试,因为现在新工具基本都默认支持跨平台 shell 习惯。
下面用一个表把两者最重要的差异列出来:
| 对比项 | CMD | PowerShell |
|---|---|---|
| 启动程序 | cmd.exe | powershell.exe 或 pwsh.exe |
| 输出模型 | 纯文本 | 结构化对象 |
| 管道内容 | 字符串流 | 对象流 |
| 内建命令名 | dir/cd/copy 等短命令 | Get-ChildItem/Set-Location 等动词-名词 |
| 脚本扩展名 | .bat/.cmd | .ps1 |
| 变量写法 | %var% | $var |
| 系统性管理能力 | 弱,只能调用外部工具 | 强,可直接操作服务、注册表、事件日志等 |
| 推荐场景 | 快速查询、旧批处理兼容 | 自动化运维、现代脚本开发 |
这个表格不是让你急着切换,而是帮你建立一种判断力:看到命令报错时,先意识到“这是哪种环境在执行它”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 同名指令看着一样,参数完全两套
2.1 先搞懂“为什么有些命令两边都能用”
新手很容易被表象误导:明明 PowerShell 里也能敲 dir、cd、cls,怎么换了个参数就报错?
关键要理解:PowerShell 之所以认识这些短命令,是因为它给很多 cmdlet 起了“别名”。dir 是 Get-ChildItem 的别名,cd 是 Set-Location 的别名,cls 是 Clear-Host 的别名。别名只是把“人话”翻译成“cmdlet”,但它不会把 CMD 的参数体系也搬过来。CMD 原版 dir 的参数用 /s、/b、/a 这种斜杠风格,而 PowerShell 的 Get-ChildItem 参数用的是 -Recurse、-Filter、-Force 这种短横线风格。
普通外部程序就不一样了。ping、ipconfig、netstat、tracert 这些其实是独立的 .exe 程序,PowerShell 只是按 PATH 环境变量找到它们并调用而已,参数解析由程序自己完成。所以你在 PowerShell 里敲 ipconfig /all 完全没问题,因为斜杠参数会被 ipconfig.exe 自己处理,跟 PowerShell 语法无关。
一句话总结:外部 exe 在两边基本都能跑,跟 PowerShell 语法无关;容易翻车的是“内建命令 + 别名 + cmdlet”这套。所以套用命令前,先问一句:这条命令到底是 exe,还是某个 shell 的内建命令?
2.2 常用指令对照表
我整理了一份最常用的对照表,覆盖平时 80% 的操作场景:
| 你想做的事 | CMD 写法 | PowerShell 写法 | 备注 |
|---|---|---|---|
| 切换目录 | cd 路径 |
Set-Location 路径 |
PowerShell 可直接 cd D:\ |
| 列出目录内容 | dir |
Get-ChildItem |
PowerShell 中 dir 是别名 |
| 清屏 | cls |
Clear-Host |
cls 别名存在 |
| 显示文件内容 | type 文件 |
Get-Content 文件 |
type 是别名 |
| 复制文件 | copy 源 目标 |
Copy-Item 源 目标 |
copy 是别名 |
| 移动文件 | move 源 目标 |
Move-Item 源 目标 |
move 是别名 |
| 删除文件 | del 文件 |
Remove-Item 文件 |
del 是别名 |
| 查找文本 | findstr 关键词 文件 |
Select-String 关键词 文件 |
参数风格完全不同 |
| 设置变量 | set var=值 |
$var = "值" |
不要混用 |
| 查看环境变量 | set PATH |
$env:PATH |
后者返回字符串 |
| 结束进程 | taskkill /PID 1234 /F |
Stop-Process -Id 1234 -Force |
taskkill 是 exe,也可直接用 |
| 查看进程 | tasklist |
Get-Process |
tasklist 是 exe |
| 查看端口占用 | netstat -ano |
Get-NetTCPConnection |
旧系统可能没有 cmdlet |
表格看完你会发现,很多命令只是“人名的马甲”,最害人的不是名字,而是参数习惯。我见过太多人把 dir /s /b 直接粘到 PowerShell 里,然后收到一屏幕红色报错。
2.3 几个典型“坑”案例拆解
第一个坑:dir /s /b 在 PowerShell 里失效。CMD 里这串命令的意思是“递归列出所有文件并只输出完整路径”,非常方便。但在 PowerShell 里,/s 会被当成一个未知参数,因为 cmdlet 的参数前缀是 -。正确的写法是:
powershell复制Get-ChildItem -Recurse -File | Select-Object -ExpandProperty FullName
这样输出的是纯路径列表。如果你还想要过滤条件,比如只要 .txt 文件,在 CMD 里会写 dir /s /b *.txt,PowerShell 里更稳的方式是:
powershell复制Get-ChildItem -Recurse -Filter *.txt | Select-Object -ExpandProperty FullName
第二个坑:跨盘切换目录。CMD 里直接 cd D:\ 不会改变当前盘符,你敲完还是停在 C 盘,得写 cd /d D:\ 才会切盘。PowerShell 不需要 /d,因为每个命令都维护着独立的当前位置,直接 cd D:\ 就能切过去。
第三个坑:PowerShell 里的 where 不是“找文件位置”的意思。CMD 老用户习惯用 where java 查找程序路径,但 PowerShell 把 where 作为 Where-Object 的别名重载了。你敲 where java,它不会告诉你 java 在哪,而是把 java 当成一个始终为真的表达式去处理。如果想在 PowerShell 里查找程序位置,要显式调用 where.exe java,注意带 .exe 后缀,这样才会走外部程序分支。
第四个坑:编码。CMD 默认使用系统代码页(中文系统通常 GBK),PowerShell 5.1 对无 BOM 的 UTF-8 文件读取可能乱码。如果你在 PowerShell 里 Get-Content 一个脚本文件发现中文乱码,不是文件坏了,很可能是编码没对上。类似问题在批处理里同样存在:用记事本另存为 ANSI 或 UTF-8 with BOM,能少很多麻烦。
2.4 文件检索与信息提取:从文本处理到属性筛选
最能体现两代命令行差距的操作,是按条件找文件。CMD 想找“最近 7 天内修改过的 txt 文件”,需要配合 forfiles 或 PowerShell 才能做,纯 CMD 写起来非常痛苦。PowerShell 天然适合这种需求:
powershell复制Get-ChildItem -Path C:\Work -Recurse -Filter *.txt |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) } |
Select-Object FullName, LastWriteTime
这个操作的本质,是把“文件属性”作为筛选项,而不是在文本结果里做关键词匹配。很多新手没意识到这个思维差异,一直用 CMD 的思路在 PowerShell 里硬怼,结果越用越别扭。
3. 脚本和自动化:变量、循环、执行策略
3.1 变量和输出的语法完全不同
对写批处理出身的人,刚接触 PowerShell 语法时最容易写混的就是变量。CMD 批处理里定义一个变量再使用它,是这样:
batch复制@echo off
set name=world
echo hello %name%
pause
PowerShell 里对应的写法是:
powershell复制$name = "world"
Write-Output "hello $name"
这里有一个极其隐蔽的坑:如果你把 CMD 的 set name=world 原样粘进 PowerShell,它不会像 CMD 里那样给变量赋值。PowerShell 会尝试把 set 当作 Set-Variable 的别名来解析,然后 name=world 被当成一个整体参数传入,大概率会得到“找不到接受参数 name=world 的位置参数”之类的报错。即使个别情况没报错,变量的行为也完全不是你想的那样。所以记住一条铁律:在 PowerShell 里写变量,统一用 $变量名 = 值,不要怀念 CMD 的 set 风格。
输出也一样。CMD 的 echo 是输出字符串,PowerShell 虽然也保留 echo 别名,但它对应的是 Write-Output cmdlet,会把对象输出到管道。如果想在 PowerShell 里原样输出带变量的一段话,直接 Write-Output "hello $name" 最直接。至于 echo hello 这种写法在 PowerShell 里也能跑,但随着脚本复杂度提升,混用风格很容易导致管道数据里混进不该出现的文本。
3.2 循环与批量处理:从 for 嵌套到管道对象
CMD 的 for 循环对新手极不友好。一个简单的“遍历当前目录下所有 .log 文件并打印文件名”,批处理要写成:
batch复制for %%f in (*.log) do echo 处理 %%f
注意批处理文件里百分号要写两个,命令行里才写一个,这点就足够劝退很多人了。PowerShell 的写法是把对象逐个丢进管道,然后对每个对象执行操作:
powershell复制Get-ChildItem -Filter *.log | ForEach-Object {
Write-Output ("处理 " + $_.Name)
}
看起来没有短到哪里去,但语义清晰很多:Get-ChildItem 负责产出文件对象,ForEach-Object 负责逐个处理,$_ 代表当前对象。更重要的是,想过滤条件时直接加 Where-Object,想排序就 Sort-Object,组合起来不会出现 CMD 里那种“为了提取一个文件名还得 split 字符串”的崩溃场面。
再比如批量重命名,把当前目录的 .log 文件统一加 .old 后缀:
powershell复制Get-ChildItem -Filter *.log | ForEach-Object {
Rename-Item -Path $_.FullName -NewName ($_.Name + ".old")
}
这段脚本的可读性非常高,任何人一眼就知道它要干什么。CMD 写同样功能并不难,难的是当你开始处理子目录、排除某些文件、判断时间条件时,批处理的复杂度会呈指数级上升。
3.3 PowerShell 执行策略:为什么脚本经常跑不了
很多新手刚写完一个 .ps1 文件,双击发现它用记事本打开了,或者右键“使用 PowerShell 运行”结果一闪而过。这不是系统坏了,而是 Windows 默认禁止双击执行 PS1 脚本,只允许你把它当作文本文件编辑。
真正让新手血压飙升的报错是这句:
powershell复制无法加载文件 xxx.ps1,因为在此系统上禁止运行脚本。
原因在于 PowerShell 的执行策略(ExecutionPolicy)。默认情况下,Windows 客户端系统是 Restricted 或 RemoteSigned,目的是防止恶意脚本随意运行。对个人电脑来说,我推荐的设置是改成 RemoteSigned,也就是“本地创建的脚本可以跑,从网络下载的脚本需要包含数字签名”:
powershell复制Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
注意我用的是 -Scope CurrentUser,不需要管理员权限,也不影响其他用户。很多教程喜欢直接让新手开管理员跑,其实个人开发场景下改当前用户就够了。
还有一个更隐蔽的误区:-ExecutionPolicy Bypass。网上大量安装教程会教你在命令行里敲:
powershell复制powershell -ep bypass -c "某段下载并执行的脚本"
初学者照做后确实能跑通,但完全没意识到 Bypass 是“不检查任何策略,命令直接执行”。如果那段命令本身是从某个网址下载脚本再执行,那你等于把系统完全开放给了一段未知代码。我自己排查问题时,就见过标题写着官方名头、实际夹带私货的脚本,所以对这类命令的态度永远是:先手动下载脚本内容读一遍,确认每一步干什么再执行。不要因为“教程复制下来的”就放松警惕。
3.4 外部程序调用与运行方式差异
如果要用 VBS 脚本去调用 PowerShell,比如隐藏窗口运行一个 ps1,常见写法是:
vbs复制Set sh = CreateObject("WScript.Shell")
sh.Run "powershell -NoProfile -ExecutionPolicy RemoteSigned -File ""D:\script.ps1""", 0, False
这里第二个参数 0 表示隐藏窗口,第三个 False 表示不等待脚本执行完就返回。整体原理是让 VBS 扮演“外部启动器”,把 PowerShell 跑起来。但这个方式对新手来说调试很不友好,
