Skip to content

Kotlin Coroutines friendly way to run an external process

License

Notifications You must be signed in to change notification settings

pgreze/kotlin-process

Folders and files

NameName
Last commit message
Last commit date

Latest commit

4f28515 Β· Jan 27, 2025

History

43 Commits
Jun 27, 2024
Jan 27, 2025
Jan 27, 2025
Jan 27, 2025
Jun 27, 2024
Jan 27, 2025
Sep 11, 2022
Jan 27, 2025
Jan 27, 2025
Feb 11, 2021
Jan 27, 2025
Jun 27, 2024
Jun 27, 2024
Jan 27, 2025

Repository files navigation

kotlin-process License Build codecov

Functional Kotlin friendly way to create external system processes by leveraging:

Installation central

repositories {
    mavenCentral()
}

dependencies {
    // Check the πŸ” maven central badge πŸ” for the latest $kotlinProcessVersion
    implementation("com.github.pgreze:kotlin-process:$kotlinProcessVersion")
}

Or in your kotlin script:

@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
@file:DependsOn("com.github.pgreze:kotlin-process:$kotlinProcessVersion")

Usage

Launch a script and consume its results

Starts a program and prints its stdout/stderr outputs to the terminal:

import com.github.pgreze.process.process
import kotlinx.coroutines.runBlocking

runBlocking {
    val res = process("echo", "hello world")

    check(res.resultCode == 0)

    // By default process output is displayed in the console.
    check(res.output == emptyList<String>())
}

The next step would probably be to capture the output stream, in order to process some data from our own-made script:

val output = process(
    "./my-script.sh", arg1, arg2,

    // Capture stdout lines to do some operations after
    stdout = Redirect.CAPTURE,

    // Default value: prints to System.err
    stderr = Redirect.PRINT,

).unwrap() // Fails if the resultCode != 0

// TODO: process the output
println("Success:\n${output.joinToString("\n")}")

Notice that if you want to capture both stdout and stderr, there will be no way to differentiate them in the returned output:

val res = process(
    "./long-and-dangerous.sh", arg1, arg2,

    // Both streams will be captured,
    // preserving their orders but mixing them in the given output.
    stdout = Redirect.CAPTURE,
    stderr = Redirect.CAPTURE,

    // Allows to consume line by line without delay the provided output.
    consumer = { line -> TODO("process $line") },
)

println("Script finished with result=${res.resultCode}")
println("stdout+stderr:\n" + res.output.joinToString("\n"))

It's also possible to redirect an output stream to a file, or manually by consuming a Flow instance.

import com.github.pgreze.process.Redirect
import com.github.pgreze.process.process
import java.io.File
import java.util.Collections
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking

val errLines = Collections.synchronizedList(mutableListOf<String>())
val res = process(
    "./my-script.sh", arg1, arg2,

    // You can save the execution result in a file,
    stdout = Redirect.ToFile(File("my-input.txt")),

    // If you want to handle this stream yourself,
    // a Flow<String> instance can be used.
    stderr = Redirect.Consume { flow -> flow.toList(errLines) },
)

The last but not least, you can just silence a stream with Redirect.SILENT 😢

Control the environment

Several other options are available to control the script environment:

import com.github.pgreze.process.InputSource
import java.io.File

val res = process(
    "./my-script.sh",
    
    // Provides the input as a string, similar to:
    // $ echo "hello world" | my-script.sh
    stdin = InputSource.fromString("hello world"),

    // Inject custom environment variables:
    env = mapOf("MY_ENV_VARIABLE" to "42"),

    // Override the working directory:
    directory = File("./a/directory"),
)

There are other ways to provide the process input:

// From a file:
process(
    "./my-script.sh",
    stdin = InputSource.FromFile(File("my-input.txt")),
)

// From an InputStream:
process(
    "./my-script.sh",
    stdin = InputSource.fromInputStream(myInputStream)),
)

// Manually by using the raw OutputStream:
process(
    "./my-script.sh",
    stdin = InputSource.FromStream { out: OutputStream ->
        out.write("hello world\n".toByteArray())
        out.flush()
    },
)

Alternative(s)

  1. https://github.com/jakubriegel/kotlin-shell
  2. https://github.com/lordcodes/turtle