【问题标题】:How to run shell command and have it print output while command runs?如何运行 shell 命令并在命令运行时打印输出?
【发布时间】:2019-03-16 11:26:39
【问题描述】:

我使用下面的函数来运行 shell 命令,但我似乎无法让它在命令运行时打印输出。例如,如果我运行 /usr/sbin/system_profiler,我必须等到命令执行完毕才能看到输出。

如何在命令仍在运行时打印 shell 命令的输出?

func runCommand(cmd: String, args: String...) -> (output: [String], error: [String], exitCode: Int32) {
print("running shell command")
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
print("shell end")
return (output, error, status)
}

【问题讨论】:

标签: swift shell


【解决方案1】:

你必须让你的任务异步和readInBackgroundAndNotify

类似这样的东西(未经测试),它使用terminationHandler 并添加readCompletionNotification 观察者来获取通知。

var output = ""

func runCommand(cmd: String, args: String..., completion: @escaping (String, String, Int32) -> Void) {
    print("running shell command")
    let task = Process()
    task.launchPath = cmd
    task.arguments = args
    let outpipe = Pipe()
    task.standardOutput = outpipe
    let errpipe = Pipe()
    task.standardError = errpipe
    task.terminationHandler = { [unowned self] returnedTask in
        NotificationCenter.default.removeObserver(self,
                                                  name: FileHandle.readCompletionNotification,
                                                  object: (returnedTask.standardOutput as! Pipe).fileHandleForReading)
        let status = returnedTask.terminationStatus
        if status == 0 {
           completion(output, "", status)
        } else {
            let errorData = errpipe.fileHandleForReading.readDataToEndOfFile()
            let errorString = String(data:errorData, encoding: .utf8)!
            completion("", errorString, status)
        }
    }
    let outputHandle = (task.standardOutput as! Pipe).fileHandleForReading
    NotificationCenter.default.addObserver(forName: FileHandle.readCompletionNotification, object: outputHandle, queue: OperationQueue.current, using: { notification in
        if let data = notification.userInfo?[NSFileHandleNotificationDataItem] as? Data, !data.isEmpty {
            output.append(String(data: data, encoding: . utf8)!)
        } else {
            task.terminate()
            return
        }
        outputHandle.readInBackgroundAndNotify()
    })
    outputHandle.readInBackgroundAndNotify()
    task.launch()
}

如果您想在接收时打印数据,将output.append(String(data: data, encoding: . utf8)!) 替换为print(String(data: data, encoding: . utf8)!),那么您可能也不需要完成处理程序。

【讨论】:

  • 为我工作,谢谢!
【解决方案2】:

具体来说,阻塞原因是outpipe.fileHandleForReading.readDataToEndOfFile()

您可以尝试以下方法来验证这一点: ...

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

    task.launch()

       repeat{
    let outdata = outpipe.fileHandleForReading.readData(ofLength: 100) //.readDataToEndOfFile()
    if var string = String(data: outdata, encoding: .utf8) {
        string = string.trimmingCharacters(in: .newlines)
        output = string.components(separatedBy: "\n")
 print(output)
    }
    let errdata = errpipe.fileHandleForReading.readData(ofLength: 100)// .readDataToEndOfFile()
    if var string = String(data: errdata, encoding: .utf8) {
        string = string.trimmingCharacters(in: .newlines)
        error = string.components(separatedBy: "\n")
        }} while (task.isRunning)

或添加 readerHandler:

 outpipe.fileHandleForReading.readabilityHandler = { file in
         let  outdata = fileHandle.readData(ofLength: 100)
            if var string = String(data: outdata, encoding: .utf8) {
                string = string.trimmingCharacters(in: .newlines)
             print( string.components(separatedBy: "\n"))

        } 

然后你可以有很多方法来避免这种情况,在 Timer 中链接 tap 或在其他线程中的 Observer 中链接。

【讨论】: