如何用Kotlin实现与sh进程的真正交互式交互?
实现与
sh进程的真正交互式交互 我正尝试与sh进程进行交互,以下是现有实现的局限及解决方案:
现有实现的问题
1. 最简实现(无控制能力)
这段代码通过inheritIO()让进程共享终端IO,但完全无法自定义输入输出的处理逻辑,无法实现可控的交互:
typealias ExitStatus = Int fun executeIO(cmd: List<String>): ExitStatus = ProcessBuilder(cmd).inheritIO().start().waitFor()
2. 固定写入次数的实现(非真正交互)
该实现能多次写入输入,但输出会被缓冲,必须等输入循环结束后才批量返回结果,不符合实时交互的要求:
fun executeNTimes(cmd: List<String>, times: Int) = runBlocking { val process = ProcessBuilder(cmd) .start() launch { process.errorStream.bufferedReader().useLines { seq -> seq.toList().forEach { println("stderr: $it") } } } launch { process.inputStream.bufferedReader().useLines { seq -> seq.toList().forEach { println("stdout: $it") } } } OutputStreamWriter(process.outputStream, "UTF-8").use { w -> repeat(times) { readln().let { println("input: $it"); w.write(it) } w.appendLine() w.flush() } } process.waitFor() }
执行效果(cmd=sh,times=2)
echo exit on stdout input: echo exit on stdout echo exit on stderr 1>&2 input: echo exit on stderr 1>&2 stderr: exit on stderr stdout: exit on stdout
问题核心:sh在非终端环境下会使用块缓冲,导致输出无法实时返回,必须等到缓冲满或进程结束才会输出内容。
期望的实时交互效果
input: echo exit on stdout stdout: exit on stdout input: echo exit on stderr 1>&2 stderr: exit on stderr input: exit
解决方案:实时流式处理输入输出
要实现真正的交互式,需解决两个核心问题:禁用sh的块缓冲、实时处理输入输出流。
完整Kotlin代码实现
import kotlinx.coroutines.* import java.io.* fun executeInteractive(cmd: List<String>) = runBlocking { // 用stdbuf强制sh使用无缓冲模式,确保输出实时返回 val command = listOf("stdbuf", "-i0", "-o0", "-e0") + cmd val process = ProcessBuilder(command) .start() // 实时读取并打印stdout launch(Dispatchers.IO) { process.inputStream.bufferedReader().use { reader -> var line: String? while (reader.readLine().also { line = it } != null) { println("stdout: $line") } } } // 实时读取并打印stderr launch(Dispatchers.IO) { process.errorStream.bufferedReader().use { reader -> var line: String? while (reader.readLine().also { line = it } != null) { println("stderr: $line") } } } // 持续读取用户输入并实时写入进程 launch(Dispatchers.IO) { OutputStreamWriter(process.outputStream, "UTF-8").use { writer -> val consoleReader = BufferedReader(InputStreamReader(System.`in`)) var input: String? while (consoleReader.readLine().also { input = it } != null) { println("input: $input") writer.write(input) writer.write(System.lineSeparator()) writer.flush() // 进程退出则终止输入循环 if (!process.isAlive) break } } } // 等待进程结束并输出退出码 val exitCode = process.waitFor() println("Process exited with code: $exitCode") } // 使用示例 fun main() { executeInteractive(listOf("sh")) }
关键细节说明
stdbuf命令:-i0禁用输入缓冲,-o0禁用标准输出缓冲,-e0禁用错误输出缓冲,强制sh实时输出内容,而非攒满缓冲块才输出。- 实时流读取:两个协程分别循环读取
stdout和stderr的每一行,读取到内容立即打印,实现实时响应。 - 持续输入循环:不限制输入次数,持续读取控制台输入直到用户中断或进程退出,每次写入后立即
flush()确保输入实时传递给sh。 - 进程存活检查:每次输入后判断进程状态,避免向已退出的进程写入数据引发异常。
验证效果
运行代码后会得到符合预期的实时交互效果:
input: echo exit on stdout stdout: exit on stdout input: echo exit on stderr 1>&2 stderr: exit on stderr input: exit Process exited with code: 0
内容的提问来源于stack exchange,提问作者Arnau Sistach Reinoso
相关产品推荐
相关产品推荐

