← ./articles-ja

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 で値を渡してください。

参考