NSTask输出缓冲区大小问题(运行SPApplicationsDataType命令)

时间:2016-02-10 15:01:51

标签: swift macos buffer nstask system-profiler

尝试从系统探查器中读取一些信息。为此,我使用NSTask运行一些终端线命令。如果我运行一些输出不太大的命令就没有问题。(例如:SPInstallHistoryDataType)但是如果我运行“SPApplicationsDataType”命令来收集已安装的应用程序列表,NSTask会等待太多而没有任何结果和任何错误。

所以我开始应该有一个缓冲区大小或类似的东西,我找不到任何相关的东西。我不知道也许我错了。

func readData (dataType: String) -> NSArray? {
let out = NSPipe()
let task = NSTask()
task.launchPath = "/usr/sbin/system_profiler"
task.arguments = ["-xml",dataType]
task.standardOutput = out
task.launch()

task.waitUntilExit()

if task.terminationStatus != 0 {
    NSLog("system_profiler returned error status")
    return nil
}

let data = out.fileHandleForReading.readDataToEndOfFile()
let plist : AnyObject?
do {
    plist = try NSPropertyListSerialization.propertyListWithData(data,
        options: [.Immutable],
        format: nil)
} catch let error as NSError {
    NSLog("%@", "Failed to parse system_profiler results. \(error.localizedDescription)")
    return nil
}

return plist as? NSArray
}
let r = readData("SPInstallHistoryDataType")// There is no problem
let r2 = readData("SPApplicationsDataType") // Crash

注意:是的我可以将此数据写入文件并从该文件中读取。但我试着了解问题所在。

2 个答案:

答案 0 :(得分:3)

这绝对是一个缓冲问题。当你一次阅读一个块时,它就可以工作。

func getApplications() -> String?
{
    var retval=""
    let theTask = NSTask()
    let taskOutput = NSPipe()
    theTask.launchPath = "/usr/sbin/system_profiler"
    theTask.standardOutput = taskOutput
    theTask.standardError = taskOutput
    theTask.arguments = ["-xml", "SPApplicationsDataType"]
    theTask.launch()

    while (true) {
        let data = taskOutput.fileHandleForReading.readDataOfLength(1024)
        if (data.length <= 0) { break }
        let str = String(data: data, encoding: NSUTF8StringEncoding)!
        retval += str

        //print (str)
    }

    theTask.waitUntilExit()

    return retval
}

答案 1 :(得分:0)

在新的Mac Pro上,我也遇到类似的问题,但更糟糕的是。使用macOS 10.15.3 Catalina,我无法获取“ SPAudioDataType”的system_profiler数据。可以调用curl等其他进程,但是system_profiler是一个问题。

我的问题非常有趣的是,仅在重新启动后大约10分钟才发生。在最初的10分钟内,无论是否使用处理程序,甚至使用上面答案中的代码“ getApplications”,一切都可以正常工作。

是的,当然,我在主线程中运行它,但是在主线程中运行与否无关紧要。

我做了很多尝试,得出这种现象的根源是什么。我发现,使用命令读取数据时我的程序挂起了

let data = taskOutput.fileHandleForReading.readDataOfLength(1024)

在存在错误数据的情况下,反之亦然,程序在使用命令读取错误消息时挂起

let data = taskError.fileHandleForReading.readDataOfLength(1024)

在有正常数据可用(但没有错误数据)的情况下。

如果我尝试获取当前可用的数据量,程序甚至会挂起:

let c = taskError.fileHandleForReading.availableData.count

无论我先进行什么测试,如果没有可用数据,程序都会挂起。

因此,我完全重写了使用异步处理程序的功能:

@discardableResult func launchprogram (_ launchpath: String, _ arguments: [String]) -> (result: String, error: Int)
{
    var out: String  = ""           // Output
    var err: String  = ""           // Error Messages
    var fin: Bool    = false        // If the process exits normally
    let pro: Process = Process()

    pro.arguments      = arguments
    pro.launchPath     = launchpath
    pro.standardOutput = Pipe()
    pro.standardError  = Pipe()
    let proOut: Pipe   = pro.standardOutput as! Pipe
    let proIn: Pipe    = pro.standardError  as! Pipe

    proOut.fileHandleForReading.readabilityHandler =
    {
        pipe in
        if let line = String(data: pipe.availableData, encoding: String.Encoding.utf8)
        {
            if line.count > 0 // Neuen Ausgabe-Text hinzufügen
            {
                out += line
            }
        }
    }

    proIn.fileHandleForReading.readabilityHandler =
    {
        pipe in
        if let line = String(data: pipe.availableData, encoding: String.Encoding.utf8)
        {
            if line.count > 0 // Neuen Fehler-Text hinzufügen
            {
                err += line
            }
        }
    }

    pro.terminationHandler =
    {
        (process) in
        fin = not(process.isRunning)
    }

    pro.launch()
    pro.waitUntilExit()

    if err == ""
    {
        if fin
        {
            return (out, 0)
        }
        else
        {
            return (out, -1)
        }
    }
    else if out == ""
    {
        let message: String = "Error while executing:" + char(13) + char(13)
        return (message + err, -2)
    }
    else
    {
        let message: String = char(13) + char(13) + "Error while executing:" + char(13) + char(13)
        return (out + message + err, -3)
    }
}

此函数与上一篇文章的“ getApplications”函数之间的根本区别在于,我使用“ handler”来管理输出和错误消息流。这始终有效。部署目标可以是10.9或更高版本。我没有在10.8和更早版本中对其进行测试。所以我的问题是,在某些情况下,在Catalina中不再可能以“正常”同步顺序获取信息,而只能使用处理程序进行异步处理。如果我中断执行,则总是处于“ libsystem_kernel.dylib read" withe the calling function "Foundation _ NSReadFromFileDescriptorWithProgress”之类的位置。我很高兴知道这是Catalina问题(使用新的Mac Pro)还是Apple希望我们使用的根本改变。