命令swift后获取终端输出

时间:2015-04-08 12:33:01

标签: swift terminal console-output

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

system("the command here")

在我想知道运行此命令的结果后,例如如果我跑

system("git status")

我想阅读有关我的回购更改的实际信息。有没有办法在swift中做到这一点?

3 个答案:

答案 0 :(得分:26)

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()的第一个参数必须是a的完整路径 可执行文件,例如"/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)
}

答案 1 :(得分:2)

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 describes

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

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

答案 2 :(得分: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)
    
}

}