我试图在Swift中为一个简单的命令行批处理脚本同步读取URL的内容.为简单起见,我使用cURL - 我知道如果必须的话,我可以使用NSURLSession.我也在swift buildOSX上使用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()
Run Code Online (Sandbox Code Playgroud)
但是,如果删除管道或更改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()
Run Code Online (Sandbox Code Playgroud)
使用来自终端的curl直接运行任何示例都会成功,因此当从特定URL(以及其他一些URL)检索时,与NSTask的交互有一些东西,并且当存在管道时,这会导致cURL失败.
在@Hod的答案上稍微扩展一下:启动过程的标准输出被重定向到管道,但是你的程序永远不会从另一个管道端读取.管道具有有限的缓冲区,例如参见 管道缓冲区有多大? 这解释了macOS上的管道缓冲区大小(最多)是64KB.
如果管道缓冲区已满,则已启动的进程无法再写入.如果进程使用阻塞I/O,那么write()管道将阻塞直到至少可以写入一个字节.在您的情况下,这永远不会发生,因此进程挂起并且不会终止.
只有当写入标准输出的数量超过管道缓冲区大小时,才会出现此问题,这解释了为什么它只发生在某些URL而不发生在其他URL上.
作为解决方案,您可以从管道中读取,例如使用
let data = pipe.fileHandleForReading.readDataToEndOfFile()
Run Code Online (Sandbox Code Playgroud)
在等待进程终止之前.另一种选择是使用异步读取,例如使用Swift实时NSTask输出到NSTextView的代码:
pipe.fileHandleForReading.readabilityHandler = { fh in
let data = fh.availableData
// process data ...
}
Run Code Online (Sandbox Code Playgroud)
这也可以通过管道从流程读取标准输出和标准错误而不会阻塞.
| 归档时间: |
|
| 查看次数: |
995 次 |
| 最近记录: |