我试图在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失败。
答案 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以仅获取数据以防万一。