WindowsでProcessBuilderの文字コード周りがどうなるか検証した

外部のプログラムにデータを渡して、その結果を得たい。 標準入出力では問題が起こりそうにないが、コマンドライン引数まわりは怪しく思える。 Windows以外の、システムのデフォルトエンコーディングUTF-8な環境なら何も問題なくイケそうだが、Windowsではどうか。ここではコマンドライン引数について検証する。

こんなのを用意して、

object OuterProcess {
  def build(commands: List[List[String]]): Option[ProcessBuilder] = {
    if (commands.nonEmpty) {
      build(commands.tail) match {
        case Some(x) => Some(commands.head #| x)
        case None => Some(commands.head)
      }
    } else {
      None
    }
  }
  def call(commands: List[List[String]]): String = {
    build(commands) match {
      case Some(x) => x !!
      case None => ""
    }
  }
}

-Dfile.encoding=UTF-8な設定で以下のテストを実施した。

class OuterProcessTest extends SpecificationWithJUnit {
  "OuterProcess.call" should {
    "receive UTF-8 encoded output" in {
      OuterProcess.call(List(
        List("cmd", "/c", "type", "text.txt")
      )) mustEqual("éindʒəl") //success
    }
    "pass unicode arguments to a program" in {
      OuterProcess.call(List(
        List("cmd", "/c", "echo", "éindʒəl")
      )) mustEqual("éindʒəl") //fail
    }
    "pass unicode arguments to a program" in {
      OuterProcess.call(List(
        List("cmd", "/c", "chcp", "65001", "&&", "cmd", "/c", "echo", "éindʒəl")
      )) mustEqual("éindʒəl") //success
    }
  }
}

というわけで、引数は正しくUnicodeなまま、対象のプログラムに渡されている。そして、このechoのように、プログラムがUnicodeな引数を正しく扱えるかどうかは、そのプログラム自体(またはその設定など)に依存するようだ。例えば、Windowsで動くgrepを用意して以下のテストを行う場合、Gowgrepでは失敗してGnupackのものでは成功する。これは前者がUnicode(マルチバイト全般?)を与えた時にうまく動作せず、後者では問題ないため。

class OuterProcessTest extends SpecificationWithJUnit {
  "OuterProcess.call" should {
    "connect programs" in {
      OuterProcess.call(List(
        List("cmd", "/c", "chcp", "65001", "&&", "cmd", "/c", "echo", "éindʒəl"),
        List("grep", "éindʒəl")
      )) mustEqual("éindʒəl")
    }
  }
}