Lazy Diary @ Hatena Blog

PowerShell / Java / miscellaneous things about software development, Tips & Gochas. CC BY-SA 4.0/Apache License 2.0

外部プロセスとしてシェルスクリプトを起動する方法

任意の処理系から外部プロセスとしてシェルスクリプトを起動したい場合、大きく以下の2つの方法が考えられます。

(1) shの引数にシェルスクリプトを指定する
/bin/sh foobar.sh のように、shの引数にシェルスクリプトを指定して起動する。
  • shに与えるパラメタはファイル・DB・環境変数から与えたりすることが多い。
  • shに与えるパラメタは多くの場合ユーザ入力に依らないので、OSコマンドインジェクションを気にせず実行できる。
(2) シェルスクリプトを直接指定する
/path/to/foobar.sh parameterのように、シェルスクリプトchmod a+xして起動する。
  • shに与えるパラメタはコマンドライン引数で指定する。
  • shに与えるパラメタはがユーザ入力に依ることがある。そのためOSコマンドインジェクションが起こりにくいよう、コマンド部分と引数部分を明確に分けて指定する。従って、コマンド部分に/bin/sh foobar.shとか指定すると"/bin/sh foobar.sh"というファイルを探しに行って実行に失敗してしまう。

(1)(2)ともに、開発環境がWindowsで本番環境がUnix系OS(よくある)とかだと、開発環境でどうやってシェルスクリプトを実行するか?という話になるわけです。これまた大きく4パターン(現在生きているのは3パターン)が考えられます。

(a) 開発環境に追加で本番環境と同じUnix系OSの実行環境を用意する
  • 実機にせよVMにせよ、追加のマシンが必要
  • 開発環境と本番環境の挙動差を気にしなくてよい
  • シェルスクリプトから起動されるミドルウェアも普通にセットアップしてしまえばよい
  • 簡単なメンテなら誰でもできるから開発環境をWindowsにしているのに、そのメリットが薄れる
  • 本番環境がAIXとかだと開発環境を大量に用意するのがしんどい
  • シェルスクリプトchmod a+xしてしまえば(1)も(2)も問題なく実行できる
(b) Git for Windows付属のBashMinGW)を使う
  • 追加のマシンが不要
  • WindowsVM上に存在する場合(AWS Workspacesなど)でも利用できる
  • シェルスクリプトから起動される各種ミドルウェアをGit for Windowsの環境上にセットアップするのは難しい
  • フォルダ構成がFHSとだいぶ違う。デフォルトではそもそも/bin/shがない(/bin/bashしかない)
  • (1)はGit for Windowsインストール先のbash.exeを指定すれば問題ないが、パスが開発環境と本番環境で異なる
  • (2)はGit for Windowsインストール時に関連づけされるが、起動方法によっては動作しない(cmd.exeからは関連付けで起動できるが、処理系からシェルを経由せずに実行しようとすると起動できない)
(c) WSLのshを使う
  • 追加のマシンが不要
  • WindowsVM上に存在する場合(AWS Workspacesなど)では使えない
  • フォルダ構成がFHSに従っている
  • initがsystemdでないのでシェルスクリプト中でsystemd関連のコマンド(systemctlなど)を呼ぶ場合は使えない
  • シェルスクリプトから起動される各種ミドルウェアがWSLに対応していない可能性がある
  • (1)はC:\Windows\System32\bash.exeを指定すれば問題ないが、パスが開発環境と本番環境で異なる
  • (2)はcmd.exeからは関連付けで起動できるが、処理系からシェルを経由せずに実行しようとすると起動できない。また関連づけを手動で設定する必要がある*1
  • (2)の変種として、.shと同名の.batを用意して、そこから.shを呼び出す方法がある。.batの内容はたとえば以下のような感じ。PowerShellを利用しているが、実行した.batの名前を組み立てられればPowerShellを使わなくても同じことができると思われる。
    @setlocal enableextensions enabledelayedexpansion & set "THIS_PATH=%~f0" & PowerShell.exe -Command "& (iex -Command ('{$PSCommandPath=\"%~f0\"; $PSScriptRoot=\"%~dp0"; #' + ((gc '!THIS_PATH:'=''!') -join \"`n\") + '}'))" %* & exit /b !errorlevel!
    $ShPath = $PSCommandPath.substring(0,$PSCommandPath.length-4) + ".sh"
    wsl ("/mnt/"+[System.Char]::ToLower($ShPath[0])+$ShPath.Substring(2,$ShPath.Length-2) -replace '\\', '/') $args;
    exit $LASTEXITCODE
    
(d) Services for UNIX (SFU)を使う
  • Windows Server 2008のころはよく使われたが、Windows Server 2012R2以降SFUが死にたえたため現在は使えない
  • 本番環境がAIXとかだとデフォルトのシェルがSFUと同じkshなので都合が良かった