Claude Code 在 Windows 下强制走 PowerShell 的配置教程
场景:Windows + pwsh 7+ 的开发环境,想让 Claude Code(以下简称 CC)所有 shell 操作走 PowerShell Tool 而不是 Bash Tool。改两个文件搞定:
CLAUDE.md锁工具选择层、settings.json把 Bash Tool 直接 deny。附实测命中率 100% 和性能基准数据。
前置:装好 PowerShell 7+
本文所有内容基于 pwsh 7+(Windows 自带的 powershell.exe 是 5.1,功能受限,不要用)。先确认或安装:
# 看本机是否已装 pwsh 7+
pwsh --version # 输出 PowerShell 7.x.x 即可没装的话三选一:
- winget(推荐):
winget install --id Microsoft.PowerShell --source winget - 手动 msi:github.com/PowerShell/PowerShell/releases 选
PowerShell-7.x.x-win-x64.msi - Microsoft Store:搜索 PowerShell 安装
完整安装指南见 官方文档。装完确认 pwsh.exe 在 PATH 里:
(Get-Command pwsh).Source # 期望 C:\Program Files\PowerShell\7\pwsh.exe省流:一句 prompt 让 CC 自己配好
点开复制这段 prompt,粘进 CC 主会话它会把两处配置合并落地(保留现有内容不覆盖)
把下面两处配置落地到我本地,保留原有内容不要覆盖,合并而不是替换: 【1】在 ~/.claude/CLAUDE.md 顶部追加以下三段 markdown: ````markdown # Global PowerShell Rules (Windows + pwsh 7+) ## Shell Preference - **默认禁止调用 Bash Tool。** 需要执行 shell 命令时只能用 PowerShell Tool(pwsh.exe)——这是工具层的硬约束。Unix 惯用语法(`ls | head`、`grep`、`curl`、`find`、`cat`)不是借口,改用对应 cmdlet(`Get-ChildItem | Select-Object -First N`、`Select-String`、`Invoke-WebRequest`、`Get-ChildItem -Recurse`、`Get-Content`)。 - 列文件用 Glob,读文件用 Read,搜内容用 Grep——dedicated tool 优先级仍然高于 shell。 - 路径一律使用 Windows 原生风格(`C:\` 或 `$env:USERPROFILE`),避免 `/` 路径;只有在调用 git/POSIX 工具时可临时接受 `/c/...`。 ## Bash Tool 例外清单(仅在以下场景允许) Bash Tool 在默认禁用之外,**仅**这几类情况可以使用,且必须在调用时一句话说明原因: 1. **用户明确指令**:"用 bash"、"跑 sh 脚本"、"在 MINGW 下执行" 等显式要求。 2. **必须依赖 POSIX 行为的脚本**:仓库里已有的 `.sh` 文件、`Makefile` 目标、`configure` 脚本——这些用 pwsh 翻译会失真,直接 Bash 调用更稳。 3. **Git hooks / pre-commit 框架**:很多 hook 假定 `/bin/sh`,pwsh 包一层反而出错。直接让 git 自己跑 Bash。 4. **跨平台 CI 脚本本地复现**:当目标是验证 `.github/workflows/*.yml` 里的 shell 步骤是否能跑通时,用 Bash 还原原始行为。 5. **MINGW-only 二进制**:`ssh-agent`、`gpg`、`openssl` 等如果只在 MSYS 环境下能正常工作。 不在上述清单的,一律走 PowerShell。模糊地带先问用户。 ## PowerShell 实战注意 - 文件写出默认 UTF-8 无 BOM(pwsh 7 默认行为);用 `Out-File`/`Set-Content` 时不要手动加 `-Encoding utf8BOM`,除非目标工具明确要求 BOM。 - 调用带空格路径的原生 exe 用 call 操作符:`& "C:\Program Files\App\app.exe" arg1`。 - 给原生命令传 `-`、`@`、`--` 开头的参数时用 stop-parsing token:`git log --% --format=%H`。 - 多行字符串(commit message、文件内容)用单引号 here-string,**闭合 `'@` 必须顶格**: ```powershell git commit -m @' feat: add foo body line '@ ``` - `-ErrorAction SilentlyContinue` 只压制输出不改退出码;要真正吞错用 `try { ... -ErrorAction Stop } catch {}`。 - 严禁 `Invoke-Expression` 拼接用户输入——命令注入风险。 - 严禁 `New-Item -Force` 用在已存在的文件上,会清空内容。 ```` 【2】把以下两项合并进 ~/.claude/settings.json(保留其他字段): ```json { "env": { "CLAUDE_CODE_USE_POWERSHELL_TOOL": "1", "POWERSHELL_TELEMETRY_OPTOUT": "1" }, "defaultShell": "powershell" } ``` 【3】合并进 ~/.claude/settings.json 的 permissions(保留已有其他元素): ```json { "permissions": { "allow": ["PowerShell", "Read", "Write", "Edit", "MultiEdit", "Glob", "WebFetch", "WebSearch", "NotebookRead", "NotebookEdit"], "deny": ["Bash"] } } ``` 改完列出具体改动点。
想看清楚每一处改了什么再动手,继续往下读。
背景:CC 为什么默认会掉进 Bash
CC 在 Windows 上同时暴露 Bash 和 PowerShell 两个 shell 工具。没有明确指令时模型按任务直觉挑工具——自然语言里一旦出现 grep、curl、find、ls -la 这类 Unix 惯用词,就容易掉进 Bash 分支。带来的问题:
- 路径风格变成
/c/Users/...,跟 PowerShell 脚本不一致 Select-String/Invoke-WebRequest/Get-ChildItem等原生 cmdlet 被绕开- Windows 下的 bash 是 MSYS2(Git Bash)模拟层,fork 代价大,实测比 pwsh 慢 2.7-31 倍(见性能基准)
下面三步:CLAUDE.md 写规则、settings.json 启用 PowerShell Tool、permissions 把 Bash 关进 deny。
步骤一:改 CLAUDE.md(告诉模型用 PowerShell)
在 C:\Users\<你>\.claude\CLAUDE.md 顶部写入三段:
# Global PowerShell Rules (Windows + pwsh 7+)
## Shell Preference
- **默认禁止调用 Bash Tool。** 需要执行 shell 命令时只能用 PowerShell Tool(pwsh.exe)——这是工具层的硬约束。Unix 惯用语法(`ls | head`、`grep`、`curl`、`find`、`cat`)不是借口,改用对应 cmdlet(`Get-ChildItem | Select-Object -First N`、`Select-String`、`Invoke-WebRequest`、`Get-ChildItem -Recurse`、`Get-Content`)。
- 列文件用 Glob,读文件用 Read,搜内容用 Grep——dedicated tool 优先级仍然高于 shell。
- 路径一律使用 Windows 原生风格(`C:\` 或 `$env:USERPROFILE`),避免 `/` 路径;只有在调用 git/POSIX 工具时可临时接受 `/c/...`。
## Bash Tool 例外清单(仅在以下场景允许)
Bash Tool 在默认禁用之外,**仅**这几类情况可以使用,且必须在调用时一句话说明原因:
1. **用户明确指令**:"用 bash"、"跑 sh 脚本"、"在 MINGW 下执行" 等显式要求。
2. **必须依赖 POSIX 行为的脚本**:仓库里已有的 `.sh` 文件、`Makefile` 目标、`configure` 脚本——这些用 pwsh 翻译会失真,直接 Bash 调用更稳。
3. **Git hooks / pre-commit 框架**:很多 hook 假定 `/bin/sh`,pwsh 包一层反而出错。直接让 git 自己跑 Bash。
4. **跨平台 CI 脚本本地复现**:当目标是验证 `.github/workflows/*.yml` 里的 shell 步骤是否能跑通时,用 Bash 还原原始行为。
5. **MINGW-only 二进制**:`ssh-agent`、`gpg`、`openssl` 等如果只在 MSYS 环境下能正常工作。
不在上述清单的,一律走 PowerShell。模糊地带先问用户。
## PowerShell 实战注意
- 文件写出默认 UTF-8 无 BOM(pwsh 7 默认行为);用 `Out-File`/`Set-Content` 时不要手动加 `-Encoding utf8BOM`,除非目标工具明确要求 BOM。
- 调用带空格路径的原生 exe 用 call 操作符:`& "C:\Program Files\App\app.exe" arg1`。
- 给原生命令传 `-`、`@`、`--` 开头的参数时用 stop-parsing token:`git log --% --format=%H`。
- 多行字符串(commit message、文件内容)用单引号 here-string,**闭合 `'@` 必须顶格**:
```powershell
git commit -m @'
feat: add foo
body line
'@
```
- `-ErrorAction SilentlyContinue` 只压制输出不改退出码;要真正吞错用 `try { ... -ErrorAction Stop } catch {}`。
- 严禁 `Invoke-Expression` 拼接用户输入——命令注入风险。
- 严禁 `New-Item -Force` 用在已存在的文件上,会清空内容。关键经验:规则要锁在「工具」而非「命令」
第一版我写的是”所有 shell 命令默认使用 PowerShell”——模糊。实测模型先挑工具再想命令,跳过了规则检查,该掉 Bash 还是掉 Bash。改成「默认禁止调用 Bash Tool」后,规则作用点直接落在工具选择层,不留翻译空间。
三段职责:
| 段落 | 作用 |
|---|---|
| Shell Preference | 硬规则:默认禁 Bash Tool + 给 PowerShell 替代 + dedicated tool 优先 + 路径风格。锁工具选择 |
| Bash Tool 例外清单 | 列出”默认禁”之外可破例的 5 类场景。给规则留弹性,避免误伤合理用例 |
| PowerShell 实战注意 | 7 条 pwsh 特有避坑点(here-string 顶格、stop-parsing、注入风险等)。让生成的命令一次跑对 |
为什么放全局而非项目级
~/.claude/CLAUDE.md 对所有项目生效,<project>/CLAUDE.md 只管该仓库。Shell 偏好是机器/OS 级别配置,与仓库无关——别在每个新仓库重写一遍。
步骤二:改 settings.json(环境变量 + 默认 shell)
在 C:\Users\<你>\.claude\settings.json 加两项:
{
"env": {
"CLAUDE_CODE_USE_POWERSHELL_TOOL": "1",
"POWERSHELL_TELEMETRY_OPTOUT": "1"
},
"defaultShell": "powershell"
}CLAUDE_CODE_USE_POWERSHELL_TOOL=1—— 官方启用 PowerShell Tool 的开关。Windows 装了 Git Bash 时这个工具默认是灰度发布,显式设1跳过灰度直接启用;Linux/macOS/WSL 同样用这个变量启用(前提是装好 pwsh 7+ 并加到 PATH)。设0可在 Windows 上反向退出灰度POWERSHELL_TELEMETRY_OPTOUT=1—— 关闭 Application Insights 遥测,省掉初始化 + 退出时的异步 flush,每次约省 10-30ms。合法值true/yes/1(三者等价)defaultShell: "powershell"—— CC settings.json 的顶级字段,控制输入框!<command>用哪个 shell(不是 Claude 自己的 Tool 选择,也不影响 hook 命令——见步骤四陷阱说明)。默认是 bash,改成 powershell 之后你在 CC 输入框打!git status直接走 pwsh
官方合法值见 about_Telemetry、PowerShell Tool docs。这些必须在 pwsh 启动前设置,所以必须放 settings.json 的 env 段。
步骤三:permissions 配置(allow 常用工具 + deny Bash)
CC 默认每次调用一个工具都要弹窗确认。常用工具加到 permissions.allow 后就不再弹;想彻底关掉某个工具,放进 permissions.deny ——比 prompt 规则和 hook 拦截都干净。在 C:\Users\<你>\.claude\settings.json 加:
{
"permissions": {
"allow": [
"PowerShell",
"Read",
"Write",
"Edit",
"MultiEdit",
"Glob",
"WebFetch",
"WebSearch",
"NotebookRead",
"NotebookEdit"
],
"deny": [
"Bash"
],
"defaultMode": "default"
}
}字段说明
allow里的纯 Tool 名等价于”允许该工具的所有调用”;要更细粒度用"PowerShell(git *)"这种前缀通配- Bash 放进 deny:把 Bash Tool 在工具层直接关掉。CC 不再把 Bash 当作可选项,主会话和子 agent 都没法绕过——比 prompt 提醒或 PreToolUse hook 更彻底,也省掉一次 pwsh 冷启动开销
defaultMode: "default"保持默认询问模式;只有 allow 内的工具免确认
真要 Bash 的时候怎么办
CLAUDE.md 里那份例外清单 5 类场景(.sh 脚本、Makefile、Git hooks、CI 复现、MINGW-only 二进制)仍然成立,只是触发方式变了:
- 能用 pwsh 嵌套就用:
& bash -c "..."在 PowerShell Tool 里跑,对短命令反而更快(见反直觉发现) - 必须用 Bash Tool 才行:临时把 settings.json 里
deny数组中的"Bash"删掉,重启 CC;用完加回去 - 直接调 exe 绕开 shell:很多场景其实只是想跑某个程序,直接
& "C:\Program Files\Git\bin\bash.exe" script.sh就行,根本不需要 Bash Tool
性能基准实测:pwsh vs bash
命中率回答”对不对”,基准回答”快不快”。本节数据跑在 Windows + pwsh 7.6 + MSYS2 Git Bash 上,6 场景 × 2 shell × 3 次串行调用,丢 run 1 冷启动,取 run 2/run 3 平均。
前提声明
- 测试机:Windows + pwsh 7.6 + MSYS2 Git Bash
- 测试目录:同一物理目录(60 md / 21 json / 277 文件)
- 重要偏差:bash 命令里的
$(date +%s%3N)在 MSYS2 下是 fork 子进程,单次约 150-300ms;pwsh 的[DateTimeOffset]::UtcNow是进程内调用约 0ms。所以 bash 的 EXEC 包含 2 次 date fork(约 400-600ms),下表 bash 数字系统性偏高 ~500ms
结果表(EXEC_MS,丢冷启动后 run2/run3 平均)
| 场景 | pwsh avg | bash avg | 倍数差 |
|---|---|---|---|
| 1. Baseline(echo) | 113 | 310 | 2.7× |
| 2. 单文件读 | 41 | 1059 | 25.8× |
| 3. FS 遍历(60 md) | 51 | 1593 | 31.2× |
| 4. 流式 grep | 51 | 943 | 18.5× |
| 5. 管道链(top-5 大) | 72 | 1340 | 18.6× |
| 6. JSON 解析 | 40 | 827 | 20.7× |
pwsh 全场胜,差距 2.7-31.2 倍。即便扣掉 bash 的 date fork 污染,bash 仍然慢 10× 以上,量级结论不变。
反直觉发现:& bash -c 嵌套反而更快
在 PowerShell Tool 里调 & bash -c "wc -l < CLAUDE.md" 耗时约 748ms,直接用 Bash Tool 做同样的事是 1059ms——嵌套比直接更快。原因:直接 Bash Tool 里有 2 次 $(date) fork,嵌套路径只 fork 1 次 bash.exe。
实战含义:pwsh 流程里偶尔掺 Unix 工具时,& bash -c "..." 一次性吞比切到 Bash Tool 便宜。
适用范围:短命令(<100ms 命令体)差异明显;长命令(>1s)里命令体才是大头,选哪个都差不多。
简短分析
为什么 bash 在 Windows 上这么慢? 根因是 MSYS2 用 CreateProcess + COW 模拟 POSIX fork(),且所有文件操作都走 POSIX→Win32 翻译层。pwsh 的 Get-ChildItem 直接调 FindFirstFile,跳过整个翻译层。场景 3 FS 遍历 51ms vs 1593ms 的 31× 差距,大头就是这层翻译。
JSON 场景(6):pwsh 的 ConvertFrom-Json 是进程内解析(40ms),bash 要 fork 出 jq 子进程(827ms)。本机 jq 用 scoop 装好了,没有”缺工具”摩擦——纯 fork 代价就能把差距拉开一个数量级。
结论:pwsh 不是风格选择更好,是在 Windows 上快一个量级。
Unix → PowerShell 命令速查
| Unix | PowerShell |
| ls / find | Get-ChildItem |
| grep | Select-String |
| cat | Get-Content(或直接用 Read 工具) |
| head -N / tail -N | Select-Object -First/-Last N |
| wc -l | Measure-Object -Line |
| sort / uniq | Sort-Object / Group-Object |
| tr / 简单 sed | -replace 运算符 |
| curl / wget | Invoke-WebRequest / Invoke-RestMethod |
| xargs | 天然管道对象流 |
| awk 复杂脚本 | ForEach-Object { ... } + -split(pwsh 更啰嗦) |
| sed -i 原地改 | (Get-Content f) -replace '...','...' \| Set-Content f |
| Heredoc <<EOF | Here-string @"..."@ / @'...'@ |
真正必须 bash 的场景(.sh 脚本、POSIX-only 工具链、Makefile 里的 bash 习惯用法),在 PowerShell Tool 里 & bash -c "..." 一行嵌套即可。
验证:命中率测试
派 subagent 跑独立任务(每个 agent 干净上下文,不会被主会话污染),让它自报调用的工具。
Agent Prompt 模板
<任务描述,自然语言>
完成后在回复最后加一行 `TOOL_USED: <工具名>`,工具名必须严格是以下之一:
`PowerShell`、`Bash`、`Grep`、`Read`、`Glob`、`Other:<名字>`。
按你实际调用的工具如实填写,不要猜。
回复控制在 120 字以内。
在主会话里并行派发多个 Agent Tool 调用,收集 TOOL_USED 汇总。
测试场景举例
| 场景 | 自然语言 | 期望工具 |
| Git 操作 | "看一下 git status 和当前分支" | PowerShell |
| Unix 词陷阱 | "grep 一下 CLAUDE.md 里的 shell" | PowerShell/Grep |
| curl 陷阱 | "curl 一下 example.com" | PowerShell |
| Dedicated tool | "读一下 CLAUDE.md" | Read(不是 shell) |
Bash 已在 deny 列表,无需测试”显式说用 bash”——CC 会直接拒绝调用。
实测结果
| 工具 | 次数 | 场景 |
| PowerShell | 8 | 所有默认 shell 任务 |
| Read / Grep | 2 | Dedicated tool 竞争正确路由 |
- 默认 shell 任务 PowerShell 命中率:100% (8/8)
- Unix 词陷阱抗性:3/3(grep、curl、find+sort 全部没掉进去)
- Dedicated tool 路由:2/2(Read/Grep 没被 shell 抢走)
一个坑:主会话自己也会偶尔违规
实测子 agent 命中率 100%,但主会话自己在 ls resources/ | head -20 上触发过 Bash Tool。根因是规则写在 system prompt 顶部,中间穿插了大量 tool output 和文件内容后,注意力会衰减。子 agent 每次都是”新人”,规则记忆新鲜;主会话是”老员工”,凭惯性办事。
这也是为什么规则要写成工具层硬约束而不是命令层软偏好——规则本身要越明确越好。步骤三的 deny 列表 就是把这条硬约束推到工具注册层:CC 根本看不到 Bash 这个选项,主会话偶尔走神也无从下手。
常见问题
Q: 简化 $PROFILE 能加快 CC 调 pwsh 吗?
A: 不能。实测用 (Get-CimInstance Win32_Process -Filter "ProcessId=$PID").CommandLine 在 CC 里跑,CC 启动 pwsh 的命令行是 pwsh.exe -NoProfile -NonInteractive -Command "...",profile 根本不加载。简化 profile 只会加快你手动开终端。
Q: 路径风格混乱(/c/Users vs C:\Users)?
A: Shell Preference 里已有”路径一律 Windows 原生风格”。如果还是出现 /c/...,通常是模型从 bash 历史输出里复制了路径;在项目 CLAUDE.md 里再强化一次即可。
Q: 模型还是会写 ls | grep xxx?
A: 这管道在 pwsh 下能跑(ls 是 Get-ChildItem 别名,grep 需要换 Select-String),但不地道。在 Shell Preference 段加一条”禁止 Unix 别名管道,用 cmdlet 全名”可以进一步修正。
Q: Dedicated tool(Read/Glob/Grep)和 shell 谁优先?
A: Dedicated tool 优先。读文件用 Read(支持分页/图片/PDF),找文件名用 Glob(按修改时间排序),搜内容用 Grep(基于 ripgrep,比 Select-String 快)。Shell Preference 第三条就是这个意思。
Q: 子 agent 会继承 Shell Preference 吗?
A: 会。CLAUDE.md 通过 system prompt 注入,user-level 和 project-level 都会继承到子 agent。实测 6 个独立 subagent 全部正确应用。例外:如果你在 Agent Tool 调用里传了自定义 system 字段覆盖,继承链断开,需要在自定义 system prompt 里重申规则。
Q: 需要 bash 跑一段命令怎么办?
A: Bash Tool 在 deny 列表里,对话里说”用 bash”也调不动。三条路:(1)在 PowerShell Tool 里 & bash -c "..." 嵌套调用——基准数据显示对短命令反而比直接 Bash Tool 更快;(2)直接调可执行文件 & "C:\Program Files\Git\bin\bash.exe" script.sh,绕开 Bash Tool;(3)真要长期用 Bash Tool,从 settings.json 的 deny 里临时删掉 "Bash" 重启 CC,用完再加回。
Q: 怎么定期验证规则还在生效?
A: 把上面的命中率测试做成 slash command 或 skill,每次升级 CC 或改全局配置后跑一遍。Bash 进 deny 后这个测试主要变成”PowerShell vs dedicated tool”的路由验证。
Q: 为什么不用 PreToolUse hook 拦截?
A: 试过。Hook 方案是给命令首 token 做 denylist(ls/cat/grep 等),命中就 deny + 提示替代命令。问题:每次触发要冷启动 pwsh 跑脚本(150-300ms),还得维护 denylist 和日志收敛曲线,而且只能拦反射性单 token,绕过路一堆(bash -c "..."、cd && ls、FOO=bar ls)。把 Bash 直接放进 permissions.deny 之后,CC 在工具注册阶段就排除了 Bash——模型连选项都看不到,零开销零维护、零绕过路径。Hook 适合”想拦但又想留弹性”的场景;想彻底关掉就用 deny。
配置清单
- 装好 pwsh 7+(前置)
C:\Users\<你>\.claude\CLAUDE.md顶部加入”Global PowerShell Rules”三段(步骤一)C:\Users\<你>\.claude\settings.json加"env": {"CLAUDE_CODE_USE_POWERSHELL_TOOL": "1", "POWERSHELL_TELEMETRY_OPTOUT": "1"}+"defaultShell": "powershell"(步骤二)C:\Users\<你>\.claude\settings.json把 PowerShell + Read/Write/Edit/Glob 等放进permissions.allow,把 Bash 放进permissions.deny(步骤三)- 跑命中率测试验证 PowerShell vs dedicated tool 路由正确
可选:
- 升级到 pwsh 7.5+ 享受 NativeAOT 冷启动优化(约省 50-100ms)
- 项目 CLAUDE.md 补业务特定 shell 约定
- 例外清单 5 类场景出现时,临时把 Bash 从 deny 移走(或用
& bash -c "..."嵌套绕过)
参考
官方文档
- about_Telemetry - PowerShell ——
POWERSHELL_TELEMETRY_OPTOUT合法值和行为 - about_Environment_Variables - PowerShell —— 环境变量设置机制
- Best Practices for Claude Code —— 官方最佳实践
社区资源
- PowerShell Style Guide —— pwsh 编码规范
- Claude Code system prompts (reverse-engineered) —— CC 内置 system prompt 原文