How to invoke external command from within Kotlin code?

后端 未结 7 1400
花落未央
花落未央 2020-11-29 22:08

I want to invoke an external command from Kotlin code. In C/Perl, I would use system() function. In Python, I would use the subprocess module. In Go, I would us

相关标签:
7条回答
  • 2020-11-29 22:27

    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()
    }
    
    0 讨论(0)
  • 2020-11-29 22:33

    For Kotlin Native:

    platform.posix.system("git status")
    

    For JVM

    Runtime.getRuntime().exec("git status")
    
    0 讨论(0)
  • 2020-11-29 22:39

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

    fun String.runCommand(
        workingDir: File = File("."),
        timeoutAmount: Long = 60,
        timeoutUnit: TimeUnit = TimeUnit.SECONDS
    ): String? = try {
        ProcessBuilder(split("\\s".toRegex()))
            .directory(workingDir)
            .redirectOutput(ProcessBuilder.Redirect.PIPE)
            .redirectError(ProcessBuilder.Redirect.PIPE)
            .start().apply { waitFor(timeoutAmount, timeoutUnit) }
            .inputStream.bufferedReader().readText()
    } catch (e: java.io.IOException) {
        e.printStackTrace()
        null
    }
    
    0 讨论(0)
  • 2020-11-29 22:40

    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.

    0 讨论(0)
  • 2020-11-29 22:43

    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
    }
    
    0 讨论(0)
  • 2020-11-29 22:46

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题