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 进程的有效参数。
我在使用 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 进程的有效参数。