PowerShell 和 Bash 最大的分歧不在语法,而在设计哲学。Bash 管道传递文本,PowerShell 管道传递对象。这一个决定带来了完全不同的工作方式:你不需要用 grepawk 从格式化输出里抠数据,你直接访问对象的属性。

1. 定位与场景

PowerShell 起初是 Windows 管理的专属工具,CMD 时代留下了太多只能做批处理的遗憾。微软在 2006 年推出 PowerShell,让系统管理员能够通过结构化的脚本操作注册表、服务、网络、Active Directory 和 Windows 组件。2016 年开源并跨平台,PowerShell Core(即今天的 pwsh)可以在 macOS 和 Linux 上运行。

什么时候该用 PowerShell

  • Windows 系统管理:服务、进程、事件日志、注册表、用户账户
  • Microsoft 生态自动化:Microsoft 365、Azure、Exchange、Entra ID 都有官方 PowerShell 模块
  • 跨平台脚本需要处理结构化数据,且不想写 Python 的时候
  • 团队协作环境以 Windows 为主,或 CI/CD 跑在 Windows runner 上

什么时候 Bash/Zsh 更合适:操作的是 Linux 服务器、需要接入大量 Unix 工具链,或者文本处理已经够用的场景,Bash 仍然是更自然的选择。两者不是互斥的,很多工程师在 macOS 上同时装了两个。

维度PowerShellBash / Zsh
管道传递结构化对象纯文本
主力平台Windows 优先,跨平台可用Unix/Linux/macOS
脚本扩展名.ps1.sh / .zsh
系统 API 访问.NET 对象模型,原生调 Windows API文件、环境变量、syscall
错误处理try/catch,异常对象$?,退出码,trap
最适合场景Windows 管理、Microsoft 云服务服务器运维、文本流处理

2. 环境入口

PowerShell 有两个版本在流通:

  • powershell.exe:Windows 内置版本(PowerShell 5.x),仅 Windows,基于 .NET Framework
  • pwsh:PowerShell 7+,跨平台,基于 .NET 6+,功能更完整,推荐使用

在 macOS/Linux 上通过包管理器安装 pwsh;Windows 上从 Microsoft Store 或 GitHub Release 安装。如果只看到 powershell 命令,检查是否需要安装新版本。

执行策略是进入 PowerShell 脚本世界必须过的第一道门。默认策略 Restricted 禁止执行任何脚本。常见设置:

# 允许本地脚本,要求远程脚本有数字签名
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

执行策略不是安全机制,而是误操作防护——它阻止你双击运行了一个来路不明的 .ps1,但对有意识运行的脚本没有约束力。真正的边界仍然是脚本来源和权限管控。

3. Cmdlet 命名规范

PowerShell 的命令叫 Cmdlet(读作 command-let),命名遵循严格的 Verb-Noun 格式。动词来自官方的 approved verb list,名词描述操作对象:

Get-Process      # 获取进程列表
Set-Location     # 切换目录(等同于 cd)
New-Item         # 创建文件或目录
Remove-Item      # 删除文件或目录
Start-Process    # 启动进程
Stop-Service     # 停止服务

常用动词:

动词含义典型用途
Get获取信息Get-ProcessGet-ServiceGet-ChildItem
Set设置或修改Set-LocationSet-ItemSet-Content
New创建对象New-ItemNew-ObjectNew-Module
Remove删除对象Remove-ItemRemove-Job
Start启动Start-ProcessStart-ServiceStart-Job
Stop停止Stop-ProcessStop-Service
Test检测或验证Test-PathTest-Connection
Export导出数据Export-CsvExport-Json
Import导入数据Import-CsvImport-Module
Invoke执行操作Invoke-CommandInvoke-WebRequest

Verb-Noun 不只是命名约定,它也是导航系统。不确定命令名时,先想动词和名词,再用 Get-Command 搜索(见第 9 节)。PowerShell 还提供了很多传统别名(lscdcatdir)让 Unix 用户有过渡期,但在脚本里应该用正式的 Cmdlet 名,别名在不同平台上不保证一致。

4. 对象管道

PowerShell 管道是理解这门语言的核心。Unix 管道把文本从左传到右,PowerShell 管道把对象从左传到右。

# Unix 风格:Get-Process 输出文本 → grep 过滤文本行
# PowerShell 风格:Get-Process 输出进程对象 → Where-Object 过滤对象
 
Get-Process | Where-Object { $_.CPU -gt 100 }

$_ 是当前管道对象,在 {} 块内用点号访问属性:

Get-Process | Where-Object { $_.Name -eq "chrome" }
Get-Service | Where-Object { $_.Status -eq "Stopped" } | Start-Service

几个常用的管道操作 Cmdlet:

Where-Object:按条件过滤,等同于 grep 但操作对象属性而不是文本:

Get-Process | Where-Object CPU -gt 100
# 新语法可以省略 {} 和 $_.,更简洁

Select-Object:选择属性,等同于 awk 取列:

Get-Process | Select-Object Name, CPU, WorkingSet | Sort-Object CPU -Descending

Sort-Object:排序:

Get-ChildItem | Sort-Object Length -Descending | Select-Object -First 10

Get-Member:查看对象有哪些属性和方法,是探索陌生 Cmdlet 输出的利器:

Get-Process | Get-Member
# 输出:TypeName、Properties、Methods

管道里传递的始终是真实对象,不是格式化后的显示文本。不要在管道末尾对表格文本做字符串解析,那是在绕过 PowerShell 最大的优势。

5. 变量与数据类型

变量以 $ 开头,动态类型,无需声明:

$name = "Alice"
$age = 30
$rate = 3.14

字符串支持双引号插值:

Write-Output "Hello, $name! You are $age years old."
# 输出:Hello, Alice! You are 30 years old.

单引号是字面量,不做插值:

Write-Output 'Hello, $name'
# 输出:Hello, $name

数组

$fruits = @("Apple", "Banana", "Cherry")
$fruits[0]              # Apple
$fruits.Count           # 3
$fruits += "Durian"     # 追加元素

哈希表(类似 Python dict):

$config = @{
    Host = "localhost"
    Port = 5432
    DB   = "mydb"
}
$config["Host"]         # localhost
$config.Port            # 5432,点号访问同样有效

6. 运算符

比较运算符用英文缩写而不是符号,避免和 XML/HTML 的 > 冲突:

运算符含义示例
-eq等于$a -eq $b
-ne不等于$a -ne $b
-gt大于$a -gt 100
-ge大于或等于$a -ge 0
-lt小于$a -lt 0
-le小于或等于$count -le 10

逻辑运算符

($a -gt 0) -and ($b -lt 100)
($status -eq "Running") -or ($status -eq "Pending")
-not $isAdmin

模式匹配运算符

"PowerShell" -match "Power"     # 正则匹配,返回 True/False
"PowerShell" -like "Power*"     # 通配符匹配,* 匹配任意字符
@(1, 2, 3) -contains 2          # 检查数组是否包含元素

算术运算符与大多数语言相同:+-*/%(取模)。字符串加法是拼接:"Hello" + " World" 得到 "Hello World"

7. 流程控制

条件语句

if ($age -ge 18) {
    Write-Output "Adult"
} elseif ($age -ge 13) {
    Write-Output "Teen"
} else {
    Write-Output "Child"
}

switch:多分支匹配,比 if-elseif 链更清晰:

switch ($day) {
    "Monday"    { Write-Output "Week starts" }
    "Friday"    { Write-Output "Almost weekend" }
    "Saturday"  { Write-Output "Weekend" }
    "Sunday"    { Write-Output "Weekend" }
    default     { Write-Output "Midweek" }
}

循环

# for:适合已知次数的迭代
for ($i = 0; $i -lt 5; $i++) {
    Write-Output "Iteration $i"
}
 
# foreach:遍历集合,最常用
$services = Get-Service
foreach ($svc in $services) {
    if ($svc.Status -eq "Stopped") {
        Write-Output "Stopped: $($svc.Name)"
    }
}
 
# 管道中的 foreach 变体(ForEach-Object)
Get-Service | ForEach-Object { Write-Output $_.Name }
 
# while:条件控制
$i = 0
while ($i -lt 3) {
    Write-Output $i
    $i++
}

8. 脚本基础

PowerShell 脚本文件扩展名为 .ps1。执行时需要用 .\ 前缀(当前目录),不能直接输入文件名:

.\hello.ps1

参数定义通过 param() 块:

# deploy.ps1
param (
    [string]$Environment = "staging",
    [int]$Port = 8080,
    [switch]$DryRun          # 开关型参数,不传则为 $false
)
 
Write-Output "Deploying to $Environment on port $Port"
if ($DryRun) {
    Write-Output "[DryRun] No actual deployment"
}

调用时使用具名参数:

.\deploy.ps1 -Environment production -Port 443
.\deploy.ps1 -Environment staging -DryRun

注释:单行用 #,多行用 <# ... #>

# 这是单行注释
 
<#
多行注释块
可以跨多行
#>

脚本里的字符串插值需要注意:$() 是子表达式,访问对象属性时要用它:

Write-Output "Service: $($svc.Name) - Status: $($svc.Status)"
# 不是 "Service: $svc.Name",那样只会插值 $svc 然后拼 ".Name"

9. 获取帮助

PowerShell 的帮助系统是内置的,不需要开浏览器:

# 查看 Cmdlet 的帮助页面
Get-Help Get-Process
 
# 查看详细帮助(含参数说明)
Get-Help Get-Process -Detailed
 
# 查看示例
Get-Help Get-Process -Examples
 
# 搜索包含关键词的 Cmdlet
Get-Help *process*
 
# 更新本地帮助文档(需要网络)
Update-Help

按动词或名词搜索 Cmdlet

# 列出所有 Get 动词的 Cmdlet
Get-Command -Verb Get
 
# 列出所有操作 Process 的 Cmdlet
Get-Command -Noun Process
 
# 搜索名字包含 "service" 的 Cmdlet
Get-Command *service*

探索对象结构

# 看某个 Cmdlet 输出了哪些属性和方法
Get-Process | Get-Member
 
# 看具体属性值(选几个感兴趣的列)
Get-Process | Select-Object Name, Id, CPU, WorkingSet | Format-Table

初学阶段,Get-HelpGet-CommandGet-Member 这三个 Cmdlet 能解答 80% 的”这个怎么用”问题。遇到陌生的对象先 Get-Member,不知道命令名先 Get-Command,知道命令名但不知道参数先 Get-Help

10. 使用原则

先看对象,再看显示Get-Process 在终端里展示成表格,但管道里是对象。终端显示是为了人眼阅读,管道里传递的不是那张表格的文本,而是底层的 .NET 对象。不确定对象有哪些属性时,Get-Member 是第一反应,不要解析表格文本。

Verb-Noun 是导航,不是背诵:不需要记住所有 Cmdlet。想获取什么就 Get-Command -Noun <名词>,想执行什么操作就 Get-Command -Verb <动词>。多数情况下你能推导出命令名,然后用 Get-Help 确认参数。

脚本要有参数,不要硬编码:把环境名、路径、端口写进 param() 而不是直接写死在脚本里。这让脚本可以被 CI、Agent 或其他人复用,不需要修改脚本本体。

执行策略不是安全边界:不要因为执行策略允许就运行来路不明的脚本。真正的判断依据是:这段脚本从哪来、做了什么、改动了哪些系统资源。在生产环境执行脚本前,先在测试环境验证,参数加 -WhatIf 或写 DryRun 逻辑,确认行为符合预期再去掉。

对象流适合自动化消费:如果脚本的输出会被另一个程序、脚本或 Agent 消费,输出对象而不是格式化文本。需要给外部系统消费时,用 ConvertTo-Json 导出结构化数据,比解析人类可读的表格稳定得多。

跨平台注意平台差异:用 pwsh 写跨平台脚本时,路径分隔符用 [IO.Path]::Combine()Join-Path,不要硬写 \;注册表、服务管理、Windows 事件日志等 Cmdlet 只在 Windows 上有效,跨平台脚本要用 $IsWindows$IsMacOS$IsLinux 做判断。