1、背景:
在定时任务中,执行完生成文件后需要IFT任务传输至其他系统,结果发现传输的文件都为空文件。排查发现在代码中使用了以下代码:
String cmd = "cp " + sourcePath + (prefixFileName+"*_"+ preDate + ".txt") + " " + destPath;
Runtime.getRuntime().exec(cmd);
2、排查分析:
将日志中打印的cmd字符串,单独在服务器上执行是没有任何问题的。(大意了,未考虑通配符的转义)
但在java代码中使用了带有通配符“*”的linux命令就会出现问题。命令中包含“*”,需要对星号进行转义,避免被当做特殊字符处理。在Linux中,星号(*)通常表示通配符,用于匹配任意字符。但在Java中,星号(*)有特殊的含义,表示乘法运算符。因此,在调用Linux命令时,需要将星号进行转义,告诉Java将其作为字符串的一部分处理。
3、解决方案:
一般在处理linux命令通配符转义的方法有两个:
(1) 使用Runtime类的exec()方法调用命令:
String command = “ls *”;
Process process = Runtime.getRuntime().exec(new String[] { “bash”, “-c”, command });
在Linux中,通过bash来执行命令,可以将命令作为一个数组传递给exec方法。
(2)使用ProcessBuilder执行命令:
String command = “ls *”;
ProcessBuilder processBuilder = new ProcessBuilder(“bash”, “-c”, command);
Process process = processBuilder.start();
使用ProcessBuilder可以更方便地创建并启动一个进程来执行命令。
(3)使用字符转义:
String command = “ls \\*”;
Process process = Runtime.getRuntime().exec(command);
在命令中的星号前添加反斜杠(\)进行转义,使其不被Java解析为特殊字符。
(4)使用单引号或双引号:
String command = “ls ‘*'”;
// String command = “ls \”*\””;
Process process = Runtime.getRuntime().exec(command);
使用单引号或双引号将星号括起来,以确保星号被传递给Linux命令解析器。
(5)使用globstar选项:
// 在命令中使用globstar选项
String command = “shopt -s globstar && ls **”;
Process process = Runtime.getRuntime().exec(new String[] { “bash”, “-c”, command });
通过启用globstar选项(`shopt -s globstar`),可以在命令中使用双星号(**)来匹配任意级别的文件和目录。
我选择的是将命令作为一个数组传递给exec方法,另外加了.waitFor();避免文件太大,cp未完成程序就结束或者异常,保证拷贝文件的完整性。代码如下:
String cmd = "cp " + sourcePath + (prefixFileName+"*_"+ preDate + ".txt") + " " + destPath;
Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",cmd}).waitFor();