在某些特定情况下,ProcessBuilder 的输入流没有响应
Getting no response from ProcessBuilder's input stream in some specific case
所以我试图从 linux 获取电池状态,到目前为止,第一个命令(路径变量)returns 非常完美,我能够从输入流,但不幸的是第二个命令(结果变量)returns 空序列。
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) */
}
输出:
upower -i /org/freedesktop/UPower/devices/battery_BAT1 | grep -E "present|state|energy-full|energy|energy-rate|time to empty|percentage"
以上输出来自最后一个命令中的also
块,只是为了预览命令的字符串以进行调试。如果我 运行 将上述命令直接输入终端,我将成功获得如下响应:
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%
为什么最后一个命令对 ProcessBuilder 不起作用(不返回任何响应)?
注:扩展函数runCommand
取自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
}
这里的问题是管道。
您正在尝试 运行 管道 — 涉及 运行 多个程序的构造,需要 shell 来解释。
但是 ProcessBuilder 运行 是一个程序。在这种情况下,它是 运行 程序 upower
并将参数传递给它 -i
、/org/freedesktop/UPower/devices/battery_BAT1
、|
、grep
、-E
,以及 "present|state|energy-full|energy|energy-rate|time to empty|percentage"
。显然 upower
不知道如何处理 |
参数或其后的参数。
您可以使用 ProcessBuilder
来 运行 创建一个 shell 实例,然后它可以 运行 您的管道;参见 this answer。
但在您自己的代码中进行过滤并避免完全调用 grep
可能会更简单、更安全、更高效。
我建议捕获进程的错误输出,这很可能会使问题变得清晰。
所以我试图从 linux 获取电池状态,到目前为止,第一个命令(路径变量)returns 非常完美,我能够从输入流,但不幸的是第二个命令(结果变量)returns 空序列。
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) */
}
输出:
upower -i /org/freedesktop/UPower/devices/battery_BAT1 | grep -E "present|state|energy-full|energy|energy-rate|time to empty|percentage"
以上输出来自最后一个命令中的also
块,只是为了预览命令的字符串以进行调试。如果我 运行 将上述命令直接输入终端,我将成功获得如下响应:
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%
为什么最后一个命令对 ProcessBuilder 不起作用(不返回任何响应)?
注:扩展函数runCommand
取自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
}
这里的问题是管道。
您正在尝试 运行 管道 — 涉及 运行 多个程序的构造,需要 shell 来解释。
但是 ProcessBuilder 运行 是一个程序。在这种情况下,它是 运行 程序 upower
并将参数传递给它 -i
、/org/freedesktop/UPower/devices/battery_BAT1
、|
、grep
、-E
,以及 "present|state|energy-full|energy|energy-rate|time to empty|percentage"
。显然 upower
不知道如何处理 |
参数或其后的参数。
您可以使用 ProcessBuilder
来 运行 创建一个 shell 实例,然后它可以 运行 您的管道;参见 this answer。
但在您自己的代码中进行过滤并避免完全调用 grep
可能会更简单、更安全、更高效。
我建议捕获进程的错误输出,这很可能会使问题变得清晰。