Java/Kotlin 将命令输出重定向到标准输出和字符串的方法

Java/Kotlin way to redirect command output to both stdout and String

我正在将代码从 NodeJS 服务器转换到 Kotlin Ktor 服务器。

NodeJS 代码将输出拆分为要由代码处理的字符串和实时服务器日志记录:

const shellScript = exec("./myScript.sh",
  (error, stdout, stderr) => {
   // this happens when the script ends
   // stdout and stderr contain full script output
    if (error === null) {
      ...
    }
  });

// Realtime server logging
shellScript.stdout.on('data', (data) => { console.log(data); });
shellScript.stderr.on('data', (data) => { console.error(data); });

根据我对java.lang.ProcessBuilder的理解,我们需要生成一个线程(或协程)来循环轮询 inputStream 和 errorStream,并将它们累积在一个 volatile 字段中。

有更简洁的方法吗?

这是我最初想避免的 ProcessBuilder 解决方案。尽管体积庞大,但它可以完成工作。如果有更好的 API 可用,请告诉我!

var logs:String = ""
runCatching {
    var command:List<String> = listOf("command", "arg")
    parameters.params?.let {command += it} // dynamic args
    ProcessBuilder(command)
        .directory(File(scriptRoot))
        .redirectOutput(ProcessBuilder.Redirect.PIPE)
        .redirectErrorStream(true) // Merges stderr into stdout
        .start().also { process -> 
            withContext(Dispatchers.IO) { // More info on this context switching : https://elizarov.medium.com/blocking-threads-suspending-coroutines-d33e11bf4761
                launch {
                    process.inputStream.bufferedReader().run {
                        while (true) { // Breaks when readLine returns null
                            readLine()?.let { line ->
                                logger.trace(line) // realtime logging
                                logs += "$line\n" // record
                            } ?: break
                        }
                    }
                }
    
                process.waitFor(60, TimeUnit.MINUTES)
                if(process.isAlive) {
                    logs += "TIMEOUT occurred".also { logger.warn(it) } + "\n"
                    process.destroy()
                }
            }
        }
}.onSuccess { process ->
    if(process.exitValue() == 0) {
        // completed with success
    } else {
        // completed with failure
    }
    
}.onFailure { ex ->
    logs = ex.stackTraceToString()
}

// Logs are available in $logs