一尘不染

通过NSTask的cURL不会终止是否存在管道

swift

我正在尝试为Swift中的简单命令行批处理脚本同步读取URL的内容。为了简单起见,我正在使用cURL-
我知道我可以使用NSURLSession。我也在swift buildOSX上使用Swift的开源版本来构建它。

问题在于,如果已将stdout重定向到管道,则在某些URL上,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()

直接使用Terminal中的curl直接运行任何示例都可以成功,因此,从该特定URL(以及其他一些URL)检索时,以及当存在管道时,与NSTask的交互存在一些问题,这将导致cURL失败。


阅读 328

收藏
2020-07-07

共1个答案

一尘不染

在@Hod的答案上稍作扩展:已启动进程的标准输出被重定向到管道,但是您的程序从不从另一管道末端读取。管道的 缓冲区有限, 例如参见
管道缓冲区有多大? 这说明macOS上的管道缓冲区大小最大为64KB。

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

仅当写入标准输出的数量超过管道缓冲区大小时,才会出现此问题,这解释了为什么仅在某些URL而不在其他URL会发生。

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

let data = pipe.fileHandleForReading.readDataToEndOfFile()


等待过程终止之前。另一个选择是使用异步读取,例如使用从Swift实时NSTask输出到NSTextView的代码:

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

这也将允许通过管道从过程中读取标准输出和标准错误而不会阻塞。

2020-07-07