
在 Java/Kotlin 中通过 Runtime.exec() 调用 shell 命令(如 kill -s SIGINT ${pid})时,若将整个命令作为单个字符串传入数组,会导致系统找不到可执行文件,引发 IOException: Cannot run program "kill -9 821": error=2, No such file or directory。根本原因在于 exec(String[]) 不经过 shell 解析,必须显式拆分命令与参数。
在 java/kotlin 中通过 runtime.exec() 调用 shell 命令(如 kill -s sigint ${pid})时,若将整个命令作为单个字符串传入数组,会导致系统找不到可执行文件,引发 ioexception: cannot run program "kill -9 821": error=2, no such file or directory。根本原因在于 exec(string[]) 不经过 shell 解析,必须显式拆分命令与参数。
Java 的 Runtime.exec(String[]) 方法不会调用 /bin/sh 或任何 shell 解析器,而是直接尝试以第一个元素为可执行程序路径、后续元素为参数的方式启动进程。因此,当你写成:
val cmdArr = arrayOf<String>("kill -s SIGINT ${process.pidValue}")
runtime.exec(cmdArr)JVM 实际会查找一个名为 "kill -s SIGINT 821"(含空格和参数的完整字符串)的可执行文件,自然失败——系统中并不存在这个文件,只有 /bin/kill。
✅ 正确做法是将命令及其各参数严格拆分为独立的数组元素:
val cmdArr = arrayOf("kill", "-s", "SIGINT", process.pidValue.toString())
runtime.exec(cmdArr)此时 JVM 会:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 将
"kill"作为程序名,在$PATH中查找(如/bin/kill); - 将
"-s"、"SIGINT"、"821"依次作为argv[1]、argv[2]、argv[3]传递给kill进程; - 由
kill自身解析信号名与 PID,行为完全等价于终端中手动执行kill -s SIGINT 821。
⚠️ 补充注意事项:
-
不要依赖 shell 特性:如管道
|、重定向>、通配符*、变量展开${pid}等,在exec(String[])中均无效;如需 shell 功能,应显式调用/bin/sh -c "..."(但会增加复杂度与安全风险); -
PID 类型转换:
process.pidValue是long,务必调用.toString()转为字符串,避免编译错误或隐式装箱异常; -
异常处理不可省略:建议包裹
try-catch (IOException | InterruptedException),并检查进程退出状态(Process.waitFor())以确认信号已送达; -
Docker 环境特殊性:容器内可能未安装
kill(精简镜像如alpine默认无/bin/kill),推荐使用busybox kill或确保基础镜像包含 POSIX 工具集(如openjdk:11-jre-slim已内置)。
总结:Java 进程启动机制要求「命令零散化」,而非「字符串拼接化」。修复此问题不仅解决 Jibri 录制 EOF 异常,更保障了 ffmpeg 进程能被优雅终止(SIGINT 触发正常 flush 和封包),从而生成结构完整、可播放的 MP4 文件。

















