ProcessBuilder 在调用 Runtime exec 工作的系统命令时失败

ProcessBuilder failing when calling a system command where Runtime exec works

我在使用 ProcessBuilder 时遇到问题,无法 运行 在服务器上执行命令。

在我项目的早期,我使用 Runtime.exec() 只是为了从一个运行良好的程序中检索输出:

private List<SatelliteCode> getSatelliteCodes() {
    List<SatelliteCode> codes = new ArrayList<>();

    Runtime runtime = Runtime.getRuntime();
    String[] commands = { "w_scan", "-s?" };
    Process process;

    try {
        process = runtime.exec(commands);

        BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));

        String s = error.readLine(); // discard first line

        while ((s = error.readLine()) != null) {
            s = s.trim();
            int i = s.indexOf('\t'); // separated by a tab!?!?
            codes.add(new SatelliteCode(s.substring(0, i), s.substring(i)));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    return codes;
}

运行 这在终端中工作正常,我得到了我需要的所有输出:

w_scan -fs -cGB -sS19E2 > channels.conf

但是,服务器需要从 'process.getErrorStream()' 抓取正在进行的输出以显示在 Web 界面中。实际发生的是 ProcessBuilder 失败并返回退出代码 1。

初始化 ProcessBuilder 和开始扫描的函数 运行ning 是 [EDIT 1]:

private static StringBuilder scan_error_output = null;

@Override
public boolean startSatelliteScan(String user, String country_code, String satellite_code) {
    UserAccountPermissions perm = validateUserEdit(user);
    if (perm == null) return false;

    Shared.writeUserLog(user, Shared.getTimeStamp() + 
            ": DVB satellite scan started " + 
            country_code + " - " + satellite_code + 
            System.lineSeparator() + System.lineSeparator());

    scan_error_output = new StringBuilder();
    new ScanThread(country_code, satellite_code).start();

    // write out country code and satellite code to prefs file

    BufferedWriter bw = null;
    try {
        bw = new BufferedWriter(new FileWriter(satellite_last_scan_codes));

        bw.write(country_code); bw.newLine();
        bw.write(satellite_code); bw.newLine();

        bw.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return true;
}

然后 运行 服务器上的另外两个线程,一个将 运行 扫描本身并等待它完成,以便它可以获得最终的扫描数据。另一个不断更新 std 错误流的输出,然后从客户端浏览器每隔一段时间轮询一次。这很像显示终端的持续输出。

扫描线程(无法启动进程)[编辑 1]:

private static class ScanThread extends Thread {
    private String cc, sc;

    public ScanThread(String country_code, String satellite_code) {
        cc = country_code;
        sc = satellite_code;
    }

    public void run() {
        ProcessBuilder pb = new ProcessBuilder("/usr/bin/w_scan", 
                "-fs", "-c" + cc, "-s" + sc);
        pb.redirectOutput(new File(satellite_scan_file));

        Process process;
        try {
            System.out.println("Scan thread started");

            process = pb.start();

            IOScanErrorOutputHandler error_output_handler = new IOScanErrorOutputHandler(process.getErrorStream());
            error_output_handler.start();

            int result = process.waitFor();
            System.out.println(cc + " - " + sc + " - " + 
                    "Process.waitFor() result " + result);

        } catch (IOException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }

        System.out.println("Scan thread finished");
    }
}

捕获输出的错误输出流线程显然由于扫描线程失败而无法启动:

private static class IOScanErrorOutputHandler extends Thread {
    private InputStream inputStream;

    IOScanErrorOutputHandler(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public void run() {
        Scanner br = null;
        try {
            System.out.println("Scan thread Error IO capture running");
            br = new Scanner(new InputStreamReader(inputStream));
            String line = null;
            while (br.hasNextLine()) {
                line = br.nextLine();
                scan_error_output.append(line + System.getProperty("line.separator"));
            }
        } finally {
            br.close();
        }
        System.out.println("Scan thread Error IO capture finished");

        scan_error_output = null;
    }
}

以及returns标准错误输出进度的服务器函数:

@Override
public String pollScanResult(String user) {
    if (validateUserEdit(user) == null) return null;
    StringBuilder sb = scan_error_output; // grab instance
    if (sb == null) return null;
    return sb.toString();
}

如上所述,Runtime.exec() 工作正常,但 ProcessBuilder 失败。

注意:我在 Linux Mint 18.1 上,使用 Apache Tomcat 8 作为服务器,linux 默认 JDK 8 和 GWT 2.7 [从 2.8 更正] 在 Eclipse 霓虹灯中。

谁能看出我做错了什么?

非常感谢...

[编辑 1]

在另一台机器上开发时,Linux Mint 17.2、JDK 8 和 Apache Tomcat 7,对于 DVB-T,此方法运行良好并轮询扫描输出出现在客户端的浏览器中。

ProcessBuilder.start 仍然是 returns 1 并且为输出扫描文件创建了一个空文件。

[编辑 2]

ProcessBuilder 失败的原因似乎是因为用户 'tomcat8' 没有 运行 'w_scan' 的权限。 'w_scan' 在终端上工作,但在 tomcat 服务器上不工作。我现在必须以某种方式解决这个问题。

[解决方案]

在 VGR 为从 ProcessBuilder 获取错误流而将其置于正确的方向之后,我开始进一步挖掘并发现我得到了:

main:3909: FATAL: failed to open '/dev/dvb/adapter0/frontend0': 13 Permission denied

Apache tomcat 8 无权访问 DVB-S 前端以进行 运行 扫描。这已通过两种方式解决:

1 - 03catalina.policy 我添加了额外的权限(我不知道它们是否有所不同)。

grant codeBase "file:/dev/dvb/-" {
    permission java.io.FilePermission "file:/dev/dvb/-", "read, write";
    permission java.security.AllPermission;
};

2 - dvb 前端属于 'video' 组。所以我需要将用户 tomcat8 添加到该组。

usermod -a -G video tomcat8

目前一切正常...

您对 ProcessBuilder 所做的事情与您对 Runtime.exec 所做的事情不同,所以我不知道您为什么认为 ProcessBuilder 是问题所在。

将命令的输出写入文件的方式存在一些问题。

首先,您的 ProcessBuilder 命令中 ">", satellite_scan_temp_file 的存在是不正确的。输出重定向不是任何命令的一部分;它由 shell 处理。但是,当您 运行 使用 Runtime.exec 或 ProcessBuilder 时,您并不是 运行 在 shell 中,而是直接执行该过程。 w_scan 和任何其他命令都不认为 > 是特殊字符。

重定向到文件的正确方法是使用 redirectOutput 方法:

ProcessBuilder pb = new ProcessBuilder(
    "/usr/bin/w_scan", "-fs", "-s" + satellite_code, "-c" + country_code);

pb.redirectOutput(new File(satellite_scan_temp_file));

其次,您的 ScanThread 代码忽略了(当前不正确的)重定向,并试图读取命令的输出。但是没有输出,因为你将它全部重定向到一个文件。

一旦正确地将输出重定向到文件,就可以完全删除 BufferedReader 和 BufferedWriter 循环。

最后,值得注意的是,您捕获的错误输出可能告诉您 > 不是 w_scan 进程的有效参数。