我正在尝试为Swift中的简单命令行批处理脚本同步读取URL的内容。为了简单起见,我正在使用cURL- 我知道我可以使用NSURLSession。我也在swift buildOSX上使用Swift的开源版本来构建它。
swift build
问题在于,如果已将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失败。
在@Hod的答案上稍作扩展:已启动进程的标准输出被重定向到管道,但是您的程序从不从另一管道末端读取。管道的 缓冲区有限, 例如参见 管道缓冲区有多大? 这说明macOS上的管道缓冲区大小最大为64KB。
如果管道缓冲区已满,则启动的进程无法再对其进行写操作。如果进程使用阻塞的I / O,则write()管道的阻塞将一直阻塞,直到可以写入至少一个字节为止。在您的情况下,这永远不会发生,因此该过程将挂起并且不会终止。
write()
仅当写入标准输出的数量超过管道缓冲区大小时,才会出现此问题,这解释了为什么仅在某些URL而不在其他URL会发生。
作为 解决方案 ,您可以从管道中读取,例如
let data = pipe.fileHandleForReading.readDataToEndOfFile()
在 等待过程终止之前。另一个选择是使用异步读取,例如使用从Swift实时NSTask输出到NSTextView的代码:
pipe.fileHandleForReading.readabilityHandler = { fh in let data = fh.availableData // process data ... }
这也将允许通过管道从过程中读取标准输出和标准错误而不会阻塞。