我正在尝试为 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 运行任何示例都会成功,因此与 NSTask 的交互有关,当从该特定 URL(和其他一些)检索时,以及当存在管道时,这会导致 cURL失败。
最佳答案
扩展一下@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 ...
}
这也允许读取标准输出和标准错误 通过管道从进程中无阻塞。
关于swift - 如果存在管道,通过 NSTask 的 cURL 不会终止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36009175/