16

我使用以下代码在终端中运行一些命令:

system("the command here")

在我想知道运行这个命令的结果是什么之后,例如,如果我运行

system("git status")

我想阅读有关我的 repo 更改的实际信息。有没有办法快速做到这一点?

4

3 回答 3

37

NSTask是将另一个程序作为子进程运行的类。您可以捕获程序的输出、错误输出、退出状态等等。

扩展我对xcode 6 swift system() command的回答,这是一个简单的实用程序函数,用于同步运行命令,并返回输出、错误输出和退出代码(现已针对 Swift 2 更新):

func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {

    var output : [String] = []
    var error : [String] = []

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

    let outpipe = NSPipe()
    task.standardOutput = outpipe
    let errpipe = NSPipe()
    task.standardError = errpipe

    task.launch()

    let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
    if var string = String.fromCString(UnsafePointer(outdata.bytes)) {
        string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
        output = string.componentsSeparatedByString("\n")
    }

    let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
    if var string = String.fromCString(UnsafePointer(errdata.bytes)) {
        string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
        error = string.componentsSeparatedByString("\n")
    }

    task.waitUntilExit()
    let status = task.terminationStatus

    return (output, error, status)
}

示例用法:

let (output, error, status) = runCommand("/usr/bin/git", args: "status")
print("program exited with status \(status)")
if output.count > 0 {
    print("program output:")
    print(output)
}
if error.count > 0 {
    print("error output:")
    print(error)
}

或者,如果您只对输出感兴趣,而不对错误消息或退出代码感兴趣:

let output = runCommand("/usr/bin/git", args: "status").output

输出和错误输出作为字符串数组返回,每行一个字符串。

的第一个参数runCommand()必须是可执行文件的完整路径,例如"/usr/bin/git". 您可以使用 shell 启动程序(这system()也是这样做的):

let (output, error, status) = runCommand("/bin/sh", args: "-c", "git status")

优点是“git”可执行文件是通过默认搜索路径自动找到的。缺点是,如果参数包含空格或其他在 shell 中具有特殊含义的字符,则必须正确引用/转义参数。


Swift 3的更新:

func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {

    var output : [String] = []
    var error : [String] = []

    let task = Process()
    task.launchPath = cmd
    task.arguments = args

    let outpipe = Pipe()
    task.standardOutput = outpipe
    let errpipe = Pipe()
    task.standardError = errpipe

    task.launch()

    let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
    if var string = String(data: outdata, encoding: .utf8) {
        string = string.trimmingCharacters(in: .newlines)
        output = string.components(separatedBy: "\n")
    }

    let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
    if var string = String(data: errdata, encoding: .utf8) {
        string = string.trimmingCharacters(in: .newlines)
        error = string.components(separatedBy: "\n")
    }

    task.waitUntilExit()
    let status = task.terminationStatus

    return (output, error, status)
}
于 2015-04-08T15:59:35.770 回答
3

system产生一个新进程,因此您无法捕获其输出。为您提供一种方法的等价物是popen,您可以像这样使用它:

import Darwin

let fp = popen("ping -c 4 localhost", "r")
var buf = Array<CChar>(count: 128, repeatedValue: 0)

while fgets(&buf, CInt(buf.count), fp) != nil,
      let str = String.fromCString(buf) {
    print(str)
}

fclose(fp)

但是,不要这样做。NSTask按照Martin 的描述使用。

编辑:根据您并行运行多个命令的请求,这里有一些可能不明智的代码:

import Darwin

let commands = [
    "tail /etc/hosts",
    "ping -c 2 localhost",
]

let fps = commands.map { popen($0, "r") }

var buf = Array<CChar>(count: 128, repeatedValue: 0)

let results: [String] = fps.map { fp  in
    var result = ""
    while fgets(&buf, CInt(buf.count), fp) != nil,
          let str = String.fromCString(buf) {
        result += str
    }
    return result
}

fps.map { fclose($0) }

println("\n\n----\n\n".join(map(zip(commands,results)) { "\($0):\n\($1)" }))

(说真的,使用NSTask

于 2015-04-08T13:06:16.330 回答
0

swift 5.x 的 2 美分,带有回调的 macOS,完成后调用。

final func doTaskFor(cmd: String, arguments: [String], callback: CallBackWithStr = nil){


let task = Process()

let absolutePath = <add your specific path..> 
let fullCmd = absolutePath+cmd

#if DEBUG
// used to debug.
let debugstr :String = fullCmd + " " + arguments.oneLine()
print(debugstr)

#endif

task.executableURL = URL(fileURLWithPath: fullCmd)
task.arguments = arguments

// Create 2 Pipes and make the task
let outPipe = Pipe()
task.standardOutput = outPipe

let errPipe = Pipe()
task.standardError = errPipe

task.terminationHandler = { (process) in
    
    print("\ndidFinish: \(!process.isRunning)")
    
    // Get the data
    let outData = outPipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: outData, encoding: .utf8)
    // print(output!)
    
    // Get the error
    let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
    let err = String(data: errData, encoding: .utf8)
    // print(err!)
    
    // usually output is empty if error.
    
    callback?(output ?? "")
    
}

do {
    try task.run()
} catch {
    let msg = " \(error)"
    Log(msg: msg, safe: true)
    print(msg)
    
}

}

于 2021-06-05T06:36:08.030 回答