MacOS环境Java Runtime.exec调用youtube-dl命令执行失败求助
Java调用MacOS终端执行命令异常问题排查
问题表现
- 执行基础命令测试时,
mkdir可正常运行,ls执行失败 - 同一份代码在Linux环境运行无异常,可正常调用
wget,但调用youtube-dl始终失败,传入youtube-dl完整全路径也无法解决 - 检索全网公开资料未找到匹配的有效解决方案
复现代码
import java.io.*; public class Main { public static void main(String[] args) throws IOException { File file = new File("links_sorted.txt"); FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr); String line; while((line=br.readLine())!=null) { String [] command = {"/bin/bash", "-c", "youtube-dl " + "'" + line + "'"}; Process proc = Runtime.getRuntime().exec(command); System.out.println(line + " done downloading"); } fr.close(); } }
故障根因
- MacOS下Java通过
Runtime.getRuntime().exec()启动的子进程是非交互式非登录shell,不会加载~/.bash_profile、~/.zshrc等用户配置文件,继承的PATH环境变量和手动打开终端时的PATH不一致。哪怕写了youtube-dl的全路径,youtube-dl运行时依赖的ffmpeg等组件如果不在默认PATH里,依然会执行失败。 - 代码启动子进程后,既没有读取进程的标准输出、标准错误流,也没有等待进程执行结束就直接打印完成提示。子进程的输出流缓冲区有大小限制,缓冲区写满后进程会直接阻塞挂死,表现为执行失败。
mkdir这类无输出的命令不会触发缓冲区阻塞,所以能正常执行;ls、youtube-dl输出内容多,MacOS环境下默认缓冲区阈值更低,很容易触发阻塞;Linux环境下默认缓冲区更大、wget等命令输出更少时不会触发该问题,和反馈的现象完全匹配。 - 手动拼接shell命令参数没有做转义处理,如果读取的链接里包含单引号、空格、特殊shell符号,会直接导致命令解析失败。
修复方案
- 启动bash时增加
-l参数,让它以登录shell模式运行,自动加载用户配置文件里的PATH等环境变量,和手动打开终端的执行环境保持一致。 - 启动子进程后单独开线程消费标准输出、标准错误流,避免缓冲区满阻塞。
- 调用
waitFor()方法等待子进程执行完毕,拿到进程退出码判断执行结果后,再输出完成提示。 - 拼接命令时对参数里的单引号做转义,避免特殊字符导致shell解析失败。
- 用try-with-resources语法自动关闭文件流、进程流,避免资源泄漏。
修复后参考代码
import java.io.*; public class Main { public static void main(String[] args) throws IOException, InterruptedException { File file = new File("links_sorted.txt"); try (BufferedReader br = new BufferedReader(new FileReader(file))) { String line; while((line=br.readLine())!=null) { // 加-l参数使用登录shell加载用户环境,同时转义参数中的单引号 String[] command = {"/bin/bash", "-lc", "youtube-dl " + "'" + line.replace("'", "'\\''") + "'"}; Process proc = Runtime.getRuntime().exec(command); // 异步消费标准错误流,避免缓冲区阻塞 new Thread(() -> { try (BufferedReader errReader = new BufferedReader(new InputStreamReader(proc.getErrorStream()))) { String errLine; while ((errLine = errReader.readLine()) != null) { System.err.println(errLine); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 异步消费标准输出流,避免缓冲区阻塞 new Thread(() -> { try (BufferedReader outReader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) { String outLine; while ((outLine = outReader.readLine()) != null) { System.out.println(outLine); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 等待进程执行完成,根据退出码判断结果 int exitCode = proc.waitFor(); if (exitCode == 0) { System.out.println(line + " done downloading"); } else { System.out.println(line + " download failed, exit code: " + exitCode); } } } } }
内容的提问来源于stack exchange,提问作者mikegrep
相关产品推荐
相关产品推荐

