有个需求,需要在本地生成数据文件,然后scp到另外一个服务器上。我是用ProcessBuilder来执行scp命令的,调试的时候发现这个scp命令会失败,报No such file or directory。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| import java.io.*; import java.util.ArrayList; import java.util.List;
public class Shell {
public static void main(String[] args) { Shell s = new Shell(); String cmd = "scp -r /data/*.json upload@10.10.10.11:/home/test/data/"; s.exec(); }
public void exec(String cmd) {
try {
String[] commandSplit = cmd.split(" "); List<String> lcommand = new ArrayList<String>(); for (int i = 0; i < commandSplit.length; i++) { lcommand.add(commandSplit[i]); } System.out.println(lcommand); ProcessBuilder processBuilder = new ProcessBuilder(lcommand); processBuilder.redirectErrorStream(true);
final Process process = processBuilder.start();
System.out.println("start run cmd=" + cmd);
new Thread() { @Override public void run() { BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = null;
try { while ((line = in.readLine()) != null) { System.out.println("output: " + line); } } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } }.start();
new Thread() { @Override public void run() { BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream())); String line = null;
try { while ((line = err.readLine()) != null) { System.out.println("err: " + line); } } catch (IOException e) { e.printStackTrace(); } finally { try { err.close(); } catch (IOException e) { e.printStackTrace(); } } } }.start();
process.waitFor(); System.out.println("finish run cmd=" + cmd); } catch (Exception e) { e.printStackTrace(); } } }
|
执行的shell命令如下:
1
| scp -r /data/*.json upload@x.x.x.x:/home/test/data/
|
因为这段代码,之前执行其他命令是没有问题的,因此猜测和这次执行的这个命令有关系。然后经过调试,发现有2个问题:
如果命令中带有星号,就会导致执行不成功
这个还没找到比较好的解决方案,目前想到两种降级方案:
将shell命令放入一个单独的文件
比如我将上述命令写入shell.sh文件,然后再通过Java去执行这个脚本文件。
这样的好处是如果后续命令有变更,只需要修改shell.sh即可,不用重新编译发布Java程序;
弊端就是如果shell命令里面包含一些和环境相关的变量,处理起来会麻烦一点。比如针对上面这个命令,如果开发环境、测试环境和正式环境的同步服务器ip不一样,那么就需要Java代码先读取到环境变量,然后根据环境变量读取对应的ip配置信息,然后传入这个shell.sh脚本。
将星号改为具体的值
比如上面这个/data/*.json,实际上在/data目录下,有3个文件(1A0001.json、399001.json、399006.json),那么我可以执行三次命令,将文件同步过去:
1 2 3
| scp -r /data/1A0001.json upload@x.x.x.x:/home/test/data/ scp -r /data/399001.json upload@x.x.x.x:/home/test/data/ scp -r /data/399006.json upload@x.x.x.x:/home/test/data/
|
这个方案仅在文件数量非常少的情况下才可以用;一旦文件数很多,这个方案就不可行了。
如果命令中带有多余的空格,也会导致执行不成功
我写的命令里面,json和upload之间有2个空格,ProcessBuilder的构造函数接收的参数是一个List,我将上面的字符串命令分割成List的时候,就会出现一个空字符串元素,进而导致最终执行命令失败。如果用Runtime.getRuntime().exec(cmd)则没有问题,因为这个方法接收的参数可以是一个完整的命令的字符串。
这个比较好解决,对分割后的List的每个元素做个处理即可:
1 2 3 4 5 6 7 8 9
| String[] commandSplit = cmd.split(" "); List<String> lcommand = new ArrayList<String>(); for (int i = 0; i < commandSplit.length; i++) { if (commandSplit[i].equals("")) { continue; } lcommand.add(commandSplit[i]); }
|