WindowsでAIにコマンドを打たせたら、94%がBashだった

Windows機で開発している。PowerShellを使っている。AIコーディングのツールもWindows版を入れている。

それなのに、33日ぶんのログでAIが実行したコマンドを数えたら、Bashが4,085回、PowerShellが282回だった。比率にして94対6。Windowsで作業しているという事実が、ほとんど反映されていない。

Windowsで作業しているのに、AIが選ぶのはほぼBash

プロジェクト別に見るともっとはっきりする。skeleton-checkとbustStudioとCloud FunctionsはPowerShellの実行回数がちょうど0。3つのプロジェクトで一度も使われていない。

例外が1つだけある。Windowsの設定ファイルを触るリポジトリでは、PowerShell 143回に対してBashが117回で、ここだけ逆転している。レジストリやプロセスを触る用があると、さすがにPowerShellが選ばれる。

なぜBashに寄るのか

理由はたぶん単純で、開発の道具がだいたいUnix前提でできているからだ。gitnpmnodecurlgrep。これらを扱うとき、Bashの構文のほうが短く書ける。パイプで繋いで awk に流すような処理を、PowerShellのオブジェクトパイプラインでやり直す理由がない。

実際にログを見ると、Bashが選ばれているのはこういう場面だ。

git log --since=2026-06-26 --numstat --format="" | awk '{add+=$1} END {print add}'
curl -s -o /dev/null -w "%{http_code}" https://example.com/
grep -rc 'TODO' src/*.ts

一方、PowerShellが選ばれているのはこういう場面。

Get-ChildItem $env:USERPROFILE -Directory -Filter ".claude*"
Start-Process "C:\path\to\image.png"

ファイルシステムやOSの機能そのものを触るときだけPowerShell、それ以外はBash。これは人間の使い分けとしても妥当で、意識せずにやっているならむしろ望ましい。

ただし、混ぜると事故る

問題は、2つのシェルが同居していることそのものにある。この1か月で実際に踏んだものを挙げる。

バックスラッシュが消える

これは今日やった。BashからPowerShellスクリプトを呼ぼうとしてこう書いた。

pwsh -File C:\Users\me\tools\check.ps1 -CheckOnly

返ってきたエラーがこれだ。

The argument 'C:Usersmetoolscheck.ps1' is not recognized as the name of a script file.

パスから区切りが全部消えている。Bashにとって \ はエスケープ文字なので、\U \m \t がそれぞれ「U」「m」「t」に化けた。Windowsのパスは、Bashに渡す時点で必ず引用符で囲むかスラッシュに直す必要がある。

pwsh -File "C:\Users\me\tools\check.ps1" -CheckOnly   # 引用符で囲む
pwsh -File C:/Users/me/tools/check.ps1 -CheckOnly     # スラッシュにする

エラーメッセージが「ファイルが見つからない」ではなく「引数が認識できない」なので、最初はスクリプト側の問題を疑ってしまった。パスが壊れていることに気づくまで少しかかった。

日本語が化ける

集計スクリプトの出力を確認したとき、こう表示された。

url����: 56
CRLF: 387 LF�̂�: 0

数字は読めるが日本語が全滅している。原因はコンソールの文字コードで、Pythonの標準出力がcp932に変換される経路を通ると、UTF-8の日本語が壊れる。中身は正しいのに表示だけ壊れているので、一瞬スクリプトのバグを疑う。

結局そのときは、ラベルを読まなくても意味が取れるように数値の並びで確認した。恒久的に直すなら、出力側をUTF-8に固定するか、そもそも日本語を出力しない設計にする。

改行コードが1行の修正を全行差分にする

いちばん高くついたのがこれだった。Windowsでスクリプトからソースファイルを書き換えるとき、たとえばPythonの open(path, 'w') は既定のテキストモードで、書き込み時に改行を LF から CRLF に変換する。

元のファイルがLFなら、1行しか変えていなくても全行が変更扱いになる。gitの差分は「500行変更」のように出て、何が変わったのか読めなくなる。しかもエラーは出ない。スクリプトは正常終了し、変更内容も正しい。壊れているのは差分の見え方だけだ。

対策は書き込みモードの指定で済む。

open(path, 'w', newline='')   # 改行を変換させない
open(path, 'wb')              # 確実なのはバイナリで読み書き

確認方法も決めておいたほうがいい。私は git diff --stat の行数で見るようにしている。実際に変えた量よりファイル全体に近い行数が出ていたら、行末が混入している。

なお、もともとCRLFのファイル(このサイトの sitemap.xml がそうだった)を触るときは逆で、CRLFを維持したまま書き換える必要がある。バイナリで読んで、CRLFで組み立てて、バイナリで書く。書いたあとに \r\n の数と \n の数が一致するか検算しておくと安心できる。

PowerShellに無いコマンドを打ってしまう

headtailwhichtouchwc -l。Bashの癖でこれらをPowerShellに投げると、当然エラーになる。逆にPowerShellの Get-Content -TotalCount をBashに投げても動かない。

シェルを切り替えた直後の1コマンド目でこれをやりがちだった。

どう使い分けるか

1か月やってみて、私が落ち着いた線引きはこうなった。

Bashを既定にする。 git、npm、node、curl、テキスト処理はこちら。開発作業の9割はここで足りる。

PowerShellはWindows固有のものを触るときだけ。 レジストリ、プロセス、環境変数の永続化、Start-Process で画像を開くなど。

シェルをまたぐときは引用符を疑う。 Bashから pwsh -File を呼ぶ、PowerShellから bash -c を呼ぶ。この境界を越える1行が、いちばんエラーになりやすい。

ファイルを書き換える処理は、改行コードを明示する。 これはシェルの話ではないが、Windowsで最も静かに壊れるところなので一緒に決めておく。

数えてみて変わったこと

正直なところ、この94対6という比率は「Windowsで開発している意味は薄いのでは」という気分にもなる数字だった。ただ、内訳を見ると納得もいく。開発の中身がWeb(静的サイト・React・Node)に寄っていて、Windows固有の処理をほとんどしていないからだ。Unityを触るプロジェクトでもBashが989回に対してPowerShellが0回だった。

逆に言うと、Windows固有の落とし穴は、Windows固有の作業をしていない場所でこそ踏む。パスの区切り、改行コード、文字コード。どれも「Windowsのことを考えていないコード」がWindows上で動くときに出る。

自分のログで同じ比率を出すのは、ツール名を数えるだけなので数分でできる。94対6という数字そのものより、自分が思っているより偏っていると分かることのほうが役に立った。