How to invoke external command from within Kotlin code?
Solution 1:
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:
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:
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