web-dev-qa-db-ja.com

Swiftスクリプトで端末コマンドを実行するにはどうすればよいですか? (例:xcodebuild)

CI bashスクリプトをSwiftに置き換えたい。 lsxcodebuildなどの通常の端末コマンドを呼び出す方法がわかりません

#!/usr/bin/env xcrun Swift

import Foundation // Works
println("Test") // Works
ls // Fails
xcodebuild -workspace myApp.xcworkspace // Fails

$ ./script.Swift
./script.Swift:5:1: error: use of unresolved identifier 'ls'
ls // Fails
^
... etc ....
58
Robert

Swiftコードでコマンド出力を使用しない場合は、次のもので十分です。

#!/usr/bin/env Swift

import Foundation

@discardableResult
func Shell(_ args: String...) -> Int32 {
    let task = Process()
    task.launchPath = "/usr/bin/env"
    task.arguments = args
    task.launch()
    task.waitUntilExit()
    return task.terminationStatus
}

Shell("ls")
Shell("xcodebuild", "-workspace", "myApp.xcworkspace")

更新:Swift3/Xcode8用

106
rintaro

コマンドライン引数を「正確に」コマンドラインで使用する場合(すべての引数を分離せずに)使用する場合は、次を試してください。

(この回答はLegoLessの回答を改善したもので、Swift 4 Xcode 9.3で使用できます)

func Shell(_ command: String) -> String {
    let task = Process()
    task.launchPath = "/bin/bash"
    task.arguments = ["-c", command]

    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String

    return output
}

// Example usage:
Shell("ls -la")
40
user3064009

ここでの問題は、BashとSwiftを組み合わせて使用​​できないことです。コマンドラインからSwiftスクリプトを実行する方法を既に知っているので、Swiftでシェルコマンドを実行するメソッドを追加する必要があります。 PracticalSwift ブログの要約:

func Shell(launchPath: String, arguments: [String]) -> String?
{
    let task = Process()
    task.launchPath = launchPath
    task.arguments = arguments

    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: String.Encoding.utf8)

    return output
}

次のSwiftコードは、xcodebuildを引数付きで実行し、結果を出力します。

Shell("xcodebuild", ["-workspace", "myApp.xcworkspace"]);

ディレクトリの内容の検索について(これはlsがBashで行うことです)、NSFileManagerを使用して、Bashの出力の代わりにSwiftで直接ディレクトリをスキャンすることをお勧めします。

30
Legoless

ユーティリティ関数In Swift 3.

これは、タスクの終了ステータスも返し、完了を待ちます。

func Shell(launchPath: String, arguments: [String] = []) -> (String? , Int32) {
    let task = Process()
    task.launchPath = launchPath
    task.arguments = arguments

    let pipe = Pipe()
    task.standardOutput = pipe
    task.standardError = pipe
    task.launch()
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: .utf8)
    task.waitUntilExit()
    return (output, task.terminationStatus)
}
20
Arun

コマンドを呼び出すためにbash環境を使用する場合は、修正版のLegolessを使用する次のbash関数を使用します。 Shell関数の結果から末尾の改行を削除する必要がありました。

Swift 3.0:(Xcode8)

import Foundation

func Shell(launchPath: String, arguments: [String]) -> String
{
    let task = Process()
    task.launchPath = launchPath
    task.arguments = arguments

    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: String.Encoding.utf8)!
    if output.characters.count > 0 {
        //remove newline character.
        let lastIndex = output.index(before: output.endIndex)
        return output[output.startIndex ..< lastIndex]
    }
    return output
}

func bash(command: String, arguments: [String]) -> String {
    let whichPathForCommand = Shell(launchPath: "/bin/bash", arguments: [ "-l", "-c", "which \(command)" ])
    return Shell(launchPath: whichPathForCommand, arguments: arguments)
}

たとえば、現在の作業ディレクトリの現在の作業gitブランチを取得するには:

let currentBranch = bash("git", arguments: ["describe", "--contains", "--all", "HEAD"])
print("current branch:\(currentBranch)")
16
Pellet

レゴレスの答えに基づいた完全なスクリプト

#!/usr/bin/env xcrun Swift

import Foundation

func printShell(launchPath: String, arguments: [AnyObject] = []) {
    let output = Shell(launchPath, arguments:arguments)

    if (output != nil) {
        println(output!)
    }
}

func Shell(launchPath: String, arguments: [AnyObject] = []) -> String? {

    let task = NSTask()
    task.launchPath = launchPath
    task.arguments = arguments

    let pipe = NSPipe()
    task.standardOutput = pipe
    task.launch()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output: String? = NSString(data: data, encoding: NSUTF8StringEncoding)

    return output
}

// > ls
// > ls -a -g
printShell("/bin/ls")
printShell("/bin/ls", arguments:["-a", "-g"])
9
Robert

Swift 4.0の更新(Stringへの変更に対処)

func Shell(launchPath: String, arguments: [String]) -> String
{
    let task = Process()
    task.launchPath = launchPath
    task.arguments = arguments

    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: String.Encoding.utf8)!
    if output.count > 0 {
        //remove newline character.
        let lastIndex = output.index(before: output.endIndex)
        return String(output[output.startIndex ..< lastIndex])
    }
    return output
}

func bash(command: String, arguments: [String]) -> String {
    let whichPathForCommand = Shell(launchPath: "/bin/bash", arguments: [ "-l", "-c", "which \(command)" ])
    return Shell(launchPath: whichPathForCommand, arguments: arguments)
}
7
rougeExciter

Appleが.launchPathとlaunch()の両方を非推奨にしているため、これを更新するために、Swift 4の更新されたユーティリティ関数があります。

注:置き換えに関するAppleのドキュメント( run()executableURL など)は、この時点では基本的に空です。

import Foundation

// wrapper function for Shell commands
// must provide full path to executable
func Shell(_ launchPath: String, _ arguments: [String] = []) -> (String?, Int32) {
  let task = Process()
  task.executableURL = URL(fileURLWithPath: launchPath)
  task.arguments = arguments

  let pipe = Pipe()
  task.standardOutput = pipe
  task.standardError = pipe

  do {
    try task.run()
  } catch {
    // handle errors
    print("Error: \(error.localizedDescription)")
  }

  let data = pipe.fileHandleForReading.readDataToEndOfFile()
  let output = String(data: data, encoding: .utf8)

  task.waitUntilExit()
  return (output, task.terminationStatus)
}


// valid directory listing test
let (goodOutput, goodStatus) = Shell("/bin/ls", ["-la"])
if let out = goodOutput { print("\(out)") }
print("Returned \(goodStatus)\n")

// invalid test
let (badOutput, badStatus) = Shell("ls")

これをプレイグラウンドに直接貼り付けて、動作を確認できる必要があります。

4
angusc

Env変数のサポートによる小さな改善:

func Shell(launchPath: String,
           arguments: [String] = [],
           environment: [String : String]? = nil) -> (String , Int32) {
    let task = Process()
    task.launchPath = launchPath
    task.arguments = arguments
    if let environment = environment {
        task.environment = environment
    }

    let pipe = Pipe()
    task.standardOutput = pipe
    task.standardError = pipe
    task.launch()
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: .utf8) ?? ""
    task.waitUntilExit()
    return (output, task.terminationStatus)
}

Swift 3に対するrintaroとLegolessの回答の混合

@discardableResult
func Shell(_ args: String...) -> String {
    let task = Process()
    task.launchPath = "/usr/bin/env"
    task.arguments = args

    let pipe = Pipe()
    task.standardOutput = pipe

    task.launch()
    task.waitUntilExit()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()

    guard let output: String = String(data: data, encoding: .utf8) else {
        return ""
    }
    return output
}
0
richy

Processクラスを使用してPythonスクリプトを実行する例。

また:

 - added basic exception handling
 - setting environment variables (in my case I had to do it to get Google SDK to authenticate correctly)
 - arguments 







 import Cocoa

func shellTask(_ url: URL, arguments:[String], environment:[String : String]) throws ->(String?, String?){
   let task = Process()
   task.executableURL = url
   task.arguments =  arguments
   task.environment = environment

   let outputPipe = Pipe()
   let errorPipe = Pipe()

   task.standardOutput = outputPipe
   task.standardError = errorPipe
   try task.run()

   let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
   let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()

   let output = String(decoding: outputData, as: UTF8.self)
   let error = String(decoding: errorData, as: UTF8.self)

   return (output,error)
}

func pythonUploadTask()
{
   let url = URL(fileURLWithPath: "/usr/bin/python")
   let pythonScript =  "upload.py"

   let fileToUpload = "/CuteCat.mp4"
   let arguments = [pythonScript,fileToUpload]
   var environment = ProcessInfo.processInfo.environment
   environment["PATH"]="usr/local/bin"
   environment["GOOGLE_APPLICATION_CREDENTIALS"] = "/Users/j.chudzynski/GoogleCredentials/credentials.json"
   do {
      let result = try shellTask(url, arguments: arguments, environment: environment)
      if let output = result.0
      {
         print(output)
      }
      if let output = result.1
      {
         print(output)
      }

   } catch  {
      print("Unexpected error:\(error)")
   }
}
0