Search code examples
kotlinprocessshinteractive

Interactive process with Kotlin


I'm trying to interact with sh. This is the easiest case I want to resolve.

The most easy is:

typealias ExitStatus = Int

fun executeIO(cmd: List<String>): ExitStatus =
    ProcessBuilder(cmd).inheritIO().start().waitFor()

But the Kotlin code doesn't have any control when the sh are executing.

When you know how many times you want to write

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()
}

But that is not interactive!

cmd = sh and 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

Is not interactive because needs to close the buffer for starting to work.

My expectation for interactive process is the next one:

input: echo exit on stdout
stdout: exit on stdout
input: echo exit on stderr 1>&2
stderr: exit on stderr
input: exit

How I can do that?


Solution

  • After read:

    The code has:

    fun executeExpect() {
        val process = ProcessBuilder(listOf("sh")).start()
        val expect = ExpectBuilder()
            .withInputs(process.inputStream, process.errorStream)
            .withOutput(process.outputStream)
            .build()
    
        fun send(str: String) {
            expect.sendLine(str)
            println("input: $str")
        }
        "echo exit on stdout".let(::send)
        expect.expect(regexp("\n$")).before.let { println("stdout: $it") }
        "echo exit on stderr 1>&2".let(::send)
        expect.expectIn(1, regexp("\n$")).before.let { println("stderr: $it") }
        "exit".let(::send)
        expect.expect(eof());
        process.waitFor()
        expect.close()
    }
    

    And now the output is:

    input: echo exit on stdout
    stdout: exit on stdout
    input: echo exit on stderr 1>&2
    stderr: exit on stderr
    input: exit