exec(String) 将整个字符串当作单一程序名查找,不解析空格为参数分隔符,也不支持shell语法(如|、>),导致路径含空格时直接报“文件不存在”;正确做法是用exec(String[])显式传入参数数组,或显式调用/bin/sh -c(Linux)或cmd /c(Windows)并严格过滤用户输入以防注入,同时必须处理I/O流、设置超时、检查退出码。

别直接用 Runtime.getRuntime().exec() 执行带空格、管道、重定向或用户输入的命令——它不解析 shell 语法,也不做参数转义,极易出错甚至被注入。
为什么 exec(String) 常常不按预期工作
传入一个完整字符串(比如 "ls -la /home/user with space")时,exec(String) 会把整个字符串当作**单个可执行文件名**去查找,而不是交给 shell 解析。空格不会被当成参数分隔符,路径里有空格就直接报 java.io.IOException: Cannot run program "ls -la /home/user with space": error=2, No such file or directory。
- 它只做简单拆分(按空格),不处理引号、反斜杠、通配符
- 无法使用
|、>、&&等 shell 特性 - 环境变量、当前工作目录、stdin/stdout/stderr 的默认行为和你终端里不一样
正确做法:用 exec(String[]) 显式拆分参数
把命令和每个参数作为独立字符串放进数组,绕过 shell 解析歧义。这是最安全、最可控的方式。
String[] cmd = {
"ffmpeg",
"-i", "/path/to/input.mp4",
"-vf", "scale=1280:720",
"/path/to/output.mp4"
};
Process process = Runtime.getRuntime().exec(cmd);
- 每个数组元素必须是干净的参数,不含多余空格或引号
- 路径含空格?没问题,只要它是一个独立数组元素(如
"/home/user name/file.txt") - 不要试图在数组里塞
"| grep keyword"—— 管道不是进程参数,是 shell 功能 - 如果真需要管道,要么改用
ProcessBuilder启多个进程手动连接流,要么显式调用/bin/sh
需要 shell 功能(如 |、>)时怎么写
显式调用系统 shell,并把原命令作为其参数传入。注意平台差异:
- Linux/macOS:
new String[]{"/bin/sh", "-c", "ls -la | grep java > out.txt"} - Windows:
new String[]{"cmd", "/c", "dir *.java > list.txt"} -
-c或/c后的整个字符串由 shell 解释,所以这里可以放心用管道、重定向、变量 - 但这也意味着:若该字符串拼接了用户输入,必须严格过滤或转义,否则就是命令注入漏洞
执行后必须处理流和退出码
不读取 process.getInputStream() 和 process.getErrorStream(),进程可能卡死(缓冲区满导致阻塞);不调用 process.waitFor() 就无法知道是否成功。
- 用
process.waitFor(30, TimeUnit.SECONDS)加超时,避免无限等待 - 用
process.exitValue()检查退出码,0通常表示成功(但不是绝对,要看具体命令约定) - 务必用
try-with-resources或手动close()输入输出流,尤其getErrorStream()容易被忽略,而错误输出不读完也会卡住
真正麻烦的从来不是“怎么启动命令”,而是“怎么确保它跑完、没卡住、结果可信、错误可捕获”。漏掉流读取或超时控制,线上服务可能悄无声息地 hang 住几个小时。

















