如果存在管道,通过 NSTask 的 cURL 不会终止
cURL through NSTask not terminating if a pipe is present
我正在尝试为 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(和其他一些)检索时,与 NSTask 的交互有一些东西,当存在管道,导致 cURL 失败。
curl 和 NSPipe 都缓冲数据。根据您在 ctrl-c out 时遇到的错误(这表明 curl 无法写入预期的数据量),您在它们之间的交互很糟糕。
尝试将 -N 选项添加到 curl 以防止它缓冲其输出。
curl也可以输出进度。我认为这不会造成问题,但您可以添加 -s 以仅获取数据以防万一。
扩展一下@Hod 的回答:启动的标准输出
进程被重定向到管道,但您的程序从未从
其他管端。管道具有 有限缓冲区, 参见示例
How big is the pipe buffer?
这解释了 macOS 上的管道缓冲区大小(最多)64KB。
如果管道缓冲区已满,则启动的进程无法写入
了。如果进程使用阻塞 I/O,那么到管道的 write()
将阻塞,直到至少可以写入一个字节。那确实
在你的情况下永远不会发生,所以进程挂起并且不会终止。
只有写入标准输出的数量才会出现这个问题
超过管道缓冲区大小,这解释了为什么它只发生在某些 URL 而不是其他 URL。
作为解决方案,您可以从管道中读取,例如与
let data = pipe.fileHandleForReading.readDataToEndOfFile()
before等待进程终止。另一种选择是
使用异步读取,例如使用 :
中的代码
pipe.fileHandleForReading.readabilityHandler = { fh in
let data = fh.availableData
// process data ...
}
这也将允许读取标准输出和标准错误
通过管道从进程无阻塞。
我正在尝试为 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(和其他一些)检索时,与 NSTask 的交互有一些东西,当存在管道,导致 cURL 失败。
curl 和 NSPipe 都缓冲数据。根据您在 ctrl-c out 时遇到的错误(这表明 curl 无法写入预期的数据量),您在它们之间的交互很糟糕。
尝试将 -N 选项添加到 curl 以防止它缓冲其输出。
curl也可以输出进度。我认为这不会造成问题,但您可以添加 -s 以仅获取数据以防万一。
扩展一下@Hod 的回答:启动的标准输出 进程被重定向到管道,但您的程序从未从 其他管端。管道具有 有限缓冲区, 参见示例 How big is the pipe buffer? 这解释了 macOS 上的管道缓冲区大小(最多)64KB。
如果管道缓冲区已满,则启动的进程无法写入
了。如果进程使用阻塞 I/O,那么到管道的 write()
将阻塞,直到至少可以写入一个字节。那确实
在你的情况下永远不会发生,所以进程挂起并且不会终止。
只有写入标准输出的数量才会出现这个问题 超过管道缓冲区大小,这解释了为什么它只发生在某些 URL 而不是其他 URL。
作为解决方案,您可以从管道中读取,例如与
let data = pipe.fileHandleForReading.readDataToEndOfFile()
before等待进程终止。另一种选择是
使用异步读取,例如使用
pipe.fileHandleForReading.readabilityHandler = { fh in
let data = fh.availableData
// process data ...
}
这也将允许读取标准输出和标准错误 通过管道从进程无阻塞。