LoginSignup
3
6

More than 5 years have passed since last update.

[macOS] プログラムからshellコマンドを呼び出す方法

Posted at

プログラムからのshellコマンドを呼び出す方法

macOSプログラムで、プログラム中からshellコマンドを呼び出す方法について説明します。
コマンド実行時の標準入力, 標準出力, 標準エラー出力を、呼び出し側に接続します。

実行環境

  • IDE: xcode 10.0
  • Programming Language: Swift

手順

  1. 入出力(パイプ)の準備
  2. 入出力の接続
  3. 終了時コールバックの接続
  4. 実行および実行終了待ち

1. 入出力(パイプ)の準備

Pipeオブジェクトを生成し、読み出しあるいは書き込みハンドラを提議します。
プログラム中のconsオブジェクトは、シェルコマンドにデータを入力し、出力を受け取るオブジェクトです。シェルコマンドからのconsオブジェクトアクセスは、アプリケーションに対して非同期である点にご注意ください。

let inpipe  = Pipe()
let outpipe = Pipe()
let errpipe = Pipe()

inpipe.fileHandleForWriting.writeabilityHandler = {
  (filehandle: FileHandle) -> Void in
  if let str = cons.scan() {
    if let data = str.data(using: .utf8) {
      filehandle.write(data)
    } else {
      NSLog("Error encoding data: \(str)")
    }
  }
}

outpipe.fileHandleForReading.readabilityHandler = {
  (_ handle: FileHandle) -> Void in
  if let str = String(data: handle.availableData, encoding: String.Encoding.utf8) {
    cons.print(string: str)
  } else {
    NSLog("Error decoding data: \(handle.availableData)")
  }
}

errpipe.fileHandleForReading.readabilityHandler = {
  (_ handle: FileHandle) -> Void in
  if let str = String(data: handle.availableData, encoding: String.Encoding.utf8) {
    cons.error(string: str)
  } else {
    NSLog("Error decoding data: \(handle.availableData)")
  }
}

2. 入出力の接続

上で生成したパイプをshellコマンド実行用プロセスに割り付けます。

let process            = Process()
process.launchPath     = "/bin/sh"
process.arguments      = ["-c", <シェルコマンドと引数の文字列>]
process.standardInput      = inpipe
process.standardOutput     = outpipe
FileHandle.standardOutput  = errpipe

3. 終了時コールバックの接続

終了時コールバックを設定し、コールバックが呼び出された時に、Read/Writeハンドラをnilでクリアするのがポイントです。これを忘れるとシェルコマンド実行終了あと、ハンドラが何度も呼び出され、プログラムがハングに陥ります(高負荷状態になります)。

process.terminationHandler = {
  (process: Process) -> Void in
  if let handler = termhdl  {
    handler(process.terminationStatus) // コールバック関数呼び出し
  }
  if let inpipe  = process.standardInput as? Pipe {
    inpipe.fileHandleForWriting.writeabilityHandler = nil
  }
  if let outpipe = process.standardOutput as? Pipe {                  
    outpipe.fileHandleForReading.readabilityHandler = nil
  }
  if let errpipe = process.standardError as? Pipe {
    errpipe.fileHandleForReading.readabilityHandler = nil
  }
}

4. 実行および実行終了待ち

プロセスを実行開始します。必要に応じてプロセスの実行終了を待ちます。

process.launch()
process.waitUntilExit()

参照

本ドキュメントで説明した実装は、自作のフレームワークCoconut Frameworkの、CNShelクラスにて実装しています。

3
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6