How to invoke external command from within Kotlin code?

ExecKotlin

Exec Problem Overview


I want to invoke an external command from Kotlin code.

  • In C/Perl, I would use the system() function.
  • In Python, I would use the subprocess module.
  • In Go, I would use os/exec, and etc.

But how do I do this in Kotlin?

Exec Solutions


Solution 1 - Exec

Example of running a git diff by shelling out:

"git diff".runCommand(gitRepoDir)

Here are two implementations of the runCommand extension function:

1. Redirect to stdout/stderr

This wires any output from the subprocess to regular stdout and stderr:

fun String.runCommand(workingDir: File) {
    ProcessBuilder(*split(" ").toTypedArray())
                .directory(workingDir)
                .redirectOutput(Redirect.INHERIT)
                .redirectError(Redirect.INHERIT)
                .start()
                .waitFor(60, TimeUnit.MINUTES)
}

2. Capturing output as a String

An alternative implementation redirecting to Redirect.PIPE instead allows you to capture output in a String:

fun String.runCommand(workingDir: File): String? {
    try {
        val parts = this.split("\\s".toRegex())
        val proc = ProcessBuilder(*parts.toTypedArray())
                .directory(workingDir)
                .redirectOutput(ProcessBuilder.Redirect.PIPE)
                .redirectError(ProcessBuilder.Redirect.PIPE)
                .start()

        proc.waitFor(60, TimeUnit.MINUTES)
        return proc.inputStream.bufferedReader().readText()
    } catch(e: IOException) {
        e.printStackTrace()
        return null
    }
}

Solution 2 - Exec

If you're running on the JVM you can just use Java Runtime exec method. e.g.

Runtime.getRuntime().exec("mycommand.sh")

You will need to have security permission to execute commands.

Solution 3 - Exec

Based on @jkschneider answer but a little more Kotlin-ified:

fun String.runCommand(
    workingDir: File = File("."),
    timeoutAmount: Long = 60,
    timeoutUnit: TimeUnit = TimeUnit.SECONDS
): String? = runCatching {
    ProcessBuilder("\\s".toRegex().split(this))
        .directory(workingDir)
        .redirectOutput(ProcessBuilder.Redirect.PIPE)
        .redirectError(ProcessBuilder.Redirect.PIPE)
        .start().also { it.waitFor(timeoutAmount, timeoutUnit) }
        .inputStream.bufferedReader().readText()
}.onFailure { it.printStackTrace() }.getOrNull()

Update: If you are in Gradle Groovy to Kotlin DSL migration all you need is this, less code you should care about and would have less unexpected (e.g. on quoted parameters) results than above code may have,

import org.codehaus.groovy.runtime.ProcessGroovyMethods

ProcessGroovyMethods.getText(ProcessGroovyMethods.execute("ls"))

// Or,
fun String.execute() = ProcessGroovyMethods.execute(this)
val Process.text: String? get() = ProcessGroovyMethods.getText(this)
// then just like groovy, "ls".execute().text

Solution 4 - Exec

For Kotlin Native:

platform.posix.system("git status")

For JVM

Runtime.getRuntime().exec("git status")

Solution 5 - Exec

I wanted a few changes from the solution of jkschneider, as it didn't catch the error codes I got from the executed commands. Also I did a few refactorings to get this:

directory exec "git status"

or

directory.execute("git", "commit", "-m", "A message")

I also opted to throw exceptions for error codes and shortened the wait, but that can easily be altered according to taste.

/**
 * Shorthand for [File.execute]. Assumes that all spaces are argument separators,
 * so no argument may contain a space.
 * ```kotlin
 *  // Example
 *  directory exec "git status"
 *
 *  // This fails since `'A` and `message'` will be considered as two arguments
 *  directory exec "git commit -m 'A message'"
 * ```
 */
infix fun File.exec(command: String): String {
    val arguments = command.split(' ').toTypedArray()
    return execute(*arguments)
}

/**
 * Executes command. Arguments may contain strings. More appropriate than [File.exec]
 * when using dynamic arguments.
 * ```kotlin
 *  // Example
 *  directory.execute("git", "commit", "-m", "A message")
 * ```
 */
fun File.execute(vararg arguments: String): String {
    val process = ProcessBuilder(*arguments)
        .directory(this)
        .start()
        .also { it.waitFor(10, TimeUnit.SECONDS) }

    if (process.exitValue() != 0) {
        throw Exception(process.errorStream.bufferedReader().readText())
    }
    return process.inputStream.bufferedReader().readText()
}

Solution 6 - Exec

As this is the first google result when searching for how to run commands in kotlin, I found my self struggeling to extend it to get basic standard input working, as the Pipe "|" is not supported for the ProcessBuilder. I've found out how to do it, and thought I'd share. I've converted jkschneider's answer to be run as a simple function, now with optional stdin and output capturing. It's a little less pretty, but more flexible. You can run it like this:

exec("someCommandWithUserInput", stdIn = "yes")
/** Run a system-level command.
 * Note: This is a system independent java exec (e.g. | doesn't work). For shell: prefix with "bash -c"
 * Inputting the string in stdIn (if any), and returning stdout and stderr as a string. */
fun exec(cmd: String, stdIn: String = "", captureOutput:Boolean = false, workingDir: File = File(".")): String? {
    try {
        val process = ProcessBuilder(*cmd.split("\\s".toRegex()).toTypedArray())
            .directory(workingDir)
            .redirectOutput(if (captureOutput) ProcessBuilder.Redirect.PIPE else ProcessBuilder.Redirect.INHERIT)
            .redirectError(if (captureOutput) ProcessBuilder.Redirect.PIPE else ProcessBuilder.Redirect.INHERIT)
            .start().apply {
                if (stdIn != "") {
                    outputStream.bufferedWriter().apply {
                        write(stdIn)
                        flush()
                        close()
                    }
                }
                waitFor(60, TimeUnit.SECONDS)
            }
        if (captureOutput) {
            return process.inputStream.bufferedReader().readText()
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
    return null
}

Solution 7 - Exec

For Kotlin/Native I use this posix-based implementation:

import kotlinx.cinterop.*
import platform.posix.*

fun executeCommand(
    command: String,
    trim: Boolean = true,
    redirectStderr: Boolean = true
): String {
    val commandToExecute = if (redirectStderr) "$command 2>&1" else command
    val fp = popen(commandToExecute, "r") ?: error("Failed to run command: $command")

    val stdout = buildString {
        val buffer = ByteArray(4096)
        while (true) {
            val input = fgets(buffer.refTo(0), buffer.size, fp) ?: break
            append(input.toKString())
        }
    }

    val status = pclose(fp)
    if (status != 0) {
        error("Command `$command` failed with status $status${if (redirectStderr) ": $stdout" else ""}")
    }

    return if (trim) stdout.trim() else stdout
}

Solution 8 - Exec

this worked for me, windows 10

ProcessBuilder("cmd /C git status".split(" "))
        .redirectOutput(ProcessBuilder.Redirect.INHERIT)
        .start()
        .waitFor()

Solution 9 - Exec

Kotlin with Okio. Clean and simple. Example: get current folder's content:

import okio.*

Runtime.getRuntime().exec("ls -l")
         .inputStream
         .source()
         .buffer()
         .use {
             with( Buffer() ){
                 writeAll(it)
                 println("Current folder's content: ${String(this.readByteArray())}")
             }
        }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBig TummyView Question on Stackoverflow
Solution 1 - ExecJonathan SchneiderView Answer on Stackoverflow
Solution 2 - ExecLionel PortView Answer on Stackoverflow
Solution 3 - ExecEbrahim ByagowiView Answer on Stackoverflow
Solution 4 - ExecTarek360View Answer on Stackoverflow
Solution 5 - ExecLoveView Answer on Stackoverflow
Solution 6 - ExecSnacOverflowView Answer on Stackoverflow
Solution 7 - ExecgildorView Answer on Stackoverflow
Solution 8 - ExecSubramanya RaoView Answer on Stackoverflow
Solution 9 - ExecInterkotView Answer on Stackoverflow