0

So I am trying to get battery status from linux, and so far the first command (path variable) returns perfectly and I am able to get its response in form of Sequence from the input stream, but unfortunately the second command (of result variable) returns empty sequence.

fun getLinuxBatteryStatus(): Nothing? {
    val path = """upower --enumerate""".runCommand() ?: return null

    val parameters = listOf("present", "state", "energy-full", "energy", "energy-rate", "time to empty", "percentage")
    val result = """upower -i ${path.first { "battery_BAT" in it }} | grep -E "${parameters.joinToString("|")}""""
        .also { println(it) }
        .runCommand() ?: return null

    result.forEach(::println)   // <- no ouput
    // println(result.count())  // <- 0

    /* Do other thing and return something (that is not related to problem) */
}

Ouput:

upower -i /org/freedesktop/UPower/devices/battery_BAT1 | grep -E "present|state|energy-full|energy|energy-rate|time to empty|percentage"

The above output is from the also block in the last command, just to preview the command's string for debugging. And if I run the above command directly into the terminal I am successfully getting the responses as follows:

    present:             yes
    state:               charging
    energy:              47.903 Wh
    energy-empty:        0 Wh
    energy-full:         50.299 Wh
    energy-full-design:  48.004 Wh
    energy-rate:         17.764 W
    percentage:          95%

Why is the last command not working (not returning any response) with the ProcessBuilder?

Note: the extension function runCommand has been taken from here

private fun String.runCommand(
    workingDir: File = File("."),
    timeoutAmount: Long = 60,
    timeoutUnit: TimeUnit = TimeUnit.SECONDS
): Sequence<String>? = try {
    ProcessBuilder(split("\\s".toRegex()))
        .directory(workingDir)
        .redirectOutput(ProcessBuilder.Redirect.PIPE)
        .redirectError(ProcessBuilder.Redirect.PIPE)
        .start()
        .apply { waitFor(timeoutAmount, timeoutUnit) }
        .inputStream.bufferedReader().lineSequence()
} catch (e: IOException) {
    e.printStackTrace()
    null
}
Animesh Sahu
  • 7,445
  • 2
  • 21
  • 49
  • Probably not your issue here, but note that that `runCommand()` function can deadlock if the process writes a significant amount to its error stream.  (Handling that would need different threads reading the input and error streams, which is more complex.) – gidds Jun 12 '20 at 16:05
  • Try printing out the command before running it (to check it gets expanded as you expect, and so you can try running it manually), and also its error stream (in case it stops with an error). – gidds Jun 12 '20 at 16:06
  • @gidds I did exactly that, printed the command to the console, and it runs perfectly in terminal as attached in the question. Btw, What is deadlocking? – Animesh Sahu Jun 12 '20 at 16:07
  • See [Wikipedia](https://en.wikipedia.org/wiki/Deadlock).  In this case, it can get into a situation where your program is doing nothing, waiting for the other process to write to its stdout stream (or terminate) — while the other process is doing nothing, waiting for your program to read its stderr.  The two processes will sit there for ever, never finishing. – gidds Jun 12 '20 at 16:12

1 Answers1

1

The problem here is the pipe.

You're trying to run a pipeline — a construction involving running multiple programs, that needs a shell to interpret.

But ProcessBuilder runs a single program.  In this case, it's running the program upower and passing it the parameters -i, /org/freedesktop/UPower/devices/battery_BAT1, |, grep, -E, and "present|state|energy-full|energy|energy-rate|time to empty|percentage".  Obviously upower won't know what to do with the | parameter or those after it.

You could use ProcessBuilder to run up a shell instance, which could then run your pipeline; see this answer.

But it would probably be simpler, safer, and more efficient to do the filtering in your own code, and avoid calling grep entirely.

I recommend capturing the process's error output, which would very probably have made the problem clear.

gidds
  • 16,558
  • 2
  • 19
  • 26
  • I actually solved the problem by removing the grep call, and replacing it by embedding filter with (inside) kotlin `in` operator essentially `contains()` call, since the return value is Sequence it is lazily evaluated so filtering is done without creating new collection :) By checking each key is contained in the parameter variable's list or not. – Animesh Sahu Jun 12 '20 at 16:29