如果存在管道,则cURL到NSTask不会终止

时间:2016-03-15 10:52:10

标签: swift nstask

我试图在Swift中为一个简单的命令行批处理脚本同步读取URL的内容。我使用cURL是为了简单起见 - 我知道如果必须的话,我可以使用NSURLSession。我也使用swift build在OSX上使用Swift的开源版本来构建它。

问题是在某些URL上,如果stdout已被重定向到管道,则NSTask永远不会终止。

// This will hang, and when terminated with Ctrl-C reports "(23) Failed writing body"
import Foundation
let task = NSTask()
let pipe = NSPipe()
task.launchPath = "/usr/bin/curl"
task.arguments = ["http://trove.nla.gov.au/newspaper/page/21704647"]
task.standardOutput = pipe
task.launch()
task.waitUntilExit()

但是,如果删除管道或更改URL,则任务成功。

// This will succeed - no pipe
import Foundation
let task = NSTask()
task.launchPath = "/usr/bin/curl"
task.arguments = ["http://trove.nla.gov.au/newspaper/page/21704647"]
task.launch()
task.waitUntilExit()

// This will succeed - different URL
import Foundation
let task = NSTask()
let pipe = NSPipe()
task.launchPath = "/usr/bin/curl"
task.arguments = ["http://trove.nla.gov.au/newspaper/page/21704646"]
task.standardOutput = pipe
task.launch()
task2.waitUntilExit()

使用来自终端的curl直接运行任何示例都会成功,因此当从特定URL(以及其他一些URL)检索时,有一些与NSTask的交互,并且当存在管道时,这会导致cURL失败。

2 个答案:

答案 0 :(得分:6)

扩大@Hod的答案:发布的标准输出 进程被重定向到一个管道,但你的程序永远不会从中读取 其他管端。管道有一个有限的缓冲区,参见例如 How big is the pipe buffer? 这解释了macOS上的管道缓冲区大小(最多)为64KB。

如果管道缓冲区已满,则启动的进程无法在其上写入 了。如果进程使用阻塞I / O,则管道的write()将被阻塞,直到可以写入至少一个字节为止。那样做 在您的情况下永远不会发生,因此该过程会挂起并且不会终止。

只有在写入标准输出的金额时才会出现问题 超过管道缓冲区大小,这解释了为什么它只发生在某些URL而不与其他URL发生。

作为解决方案,您可以从管道中读取,例如与

let data = pipe.fileHandleForReading.readDataToEndOfFile()
在等待进程终止之前

。另一种选择是 使用异步读取,例如使用Real time NSTask output to NSTextView with Swift的代码:

pipe.fileHandleForReading.readabilityHandler = { fh in
    let data = fh.availableData
    // process data ...
}

这也可以读取标准输出和标准错误 从一个过程通过管道而不阻塞。

答案 1 :(得分:3)

curl和NSPipe缓冲区数据。根据你在ctrl-c out时得到的错误(这表明curl无法写出预期的数据量),你们之间的互动很糟糕。

尝试将-N选项添加到curl以防止它缓冲其输出。

curl也可以输出进度。我不认为这会导致问题,但您可以添加-s以仅获取数据以防万一。