4
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

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

プログラムからの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クラスにて実装しています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
4
Help us understand the problem. What are the problem?