Bash経由のPowerShell Where-Objectが壊れる原因は変数展開
PowerShellで直接実行すると動くのに、BashやGit Bash経由で呼ぶと壊れるコマンドがあります。
特に壊れやすいのが Where-Object を含むコマンドです。
powershell -Command "Get-Process | Where-Object { $_.Id -eq $target_pid }"
一見PowerShellの構文に見えますが、PowerShellへ届く前にBashが $target_pid や $_ を解釈します。その結果、PowerShellが受け取る文字列は元の意図と違うものになります。
症状
よくある見え方は次の通りです。
- PowerShellのparser errorが出る
Where-Objectの値が空になる- 対象プロセスがあるのに何も返らない
\や式の欠落に関するエラーが出る
PowerShell単体では動くのに、BashやGit Bash、Bash経由のツールから呼ぶと落ちるなら、shell境界を疑います。
原因
コマンドは2段階で解釈されます。
Bashが先に文字列を解釈する
PowerShellは解釈後の文字列を受け取る
次のコマンドでは、Bashが $target_pid を展開しようとします。
powershell -Command "Get-Process | Where-Object { $_.Id -eq $target_pid }"
Bash側にその変数がなければ空文字になります。$_ もPowerShellの自動変数としてではなく、Bash側で先に触られる可能性があります。
結果としてPowerShellには壊れたscript blockが届きます。
バックスラッシュで直そうとしない
次のような修正は避けます。
powershell -Command "Get-Process | Where-Object { \$_.Id -eq \$target_pid }"
\$ はBash的なエスケープです。PowerShellのscript blockでは期待通りに扱われず、逆に構文エラーになることがあります。
一番安全な回避策
できるだけPowerShellの中で完結させます。
powershell -Command "if (Get-Process -Name node -ErrorAction SilentlyContinue) { Write-Output 'running' } else { Write-Output 'stopped' }"
Bash変数をPowerShell文字列の中へ入れないため、壊れる場所が減ります。
値を渡すならArgumentList
Bash側の値をPowerShellへ渡す必要があるなら、param() と -ArgumentList を使います。
target_pid=1234
powershell -Command "param([int]$pid) if (Get-Process -Id $pid -ErrorAction SilentlyContinue) { Write-Output 'found' }" -ArgumentList "$target_pid"
PowerShell側では $pid として受け取れるため、Where-Object の中にBash変数を直接埋め込む必要がありません。
チェックリスト
Bash経由のPowerShellが壊れる時は、次を確認します。
-Commandの中に$があるかWhere-Object { $_ ... }をBashのdouble quote内に入れていないか- PowerShellだけで実行すると動くか
- 値渡しを
-ArgumentListにできないか - そもそもPowerShell script fileに逃がせないか
まとめ
これはPowerShellの構文ミスではなく、shell境界の問題です。
Bashが $... を先に展開するため、PowerShellへ届く時点で壊れています。自動化では、PowerShell内で完結させるか、-ArgumentList で値を渡してください。