最近在做一个项目,经常需要写 bat 脚本,后来由于需要把bat脚本的内容内嵌代码中,从而引发了一些问题。

执行启动Mysql命令

问题:在 Java 中执行 C:\Program Files\MySQL\MySQL Server 5.1\bin\mysqld --defaults-file=C:\Program Files\MySQL\MySQL Server 5.1\bin\my.ini 这条命令? 初次看到这条命令时,很自然的就想到了以下的方式:

@Test
public void cmdTest1() {
    String sCommand = "\"C:\\Program Files\\MySQL\\MySQL Server 5.1\\bin\\mysqld\" --defaults-file=\"C:\\Program Files\\MySQL\\MySQL Server 5.1\\bin\\my.ini\""; 
    try {
		Runtime.getRuntime().exec(new String[]{"cmd","/c",sCommand});
	} catch (IOException e) {
		System.out.println("command :"+cmd+" "+e.getMessage());
 

这段代码从实现上看没有什么大的问题,然而在实际运行中却无法启动 Mysql 。那么这是什么原因呢?

这还要从 JDK 源码中找到答案。本文以 jdk1.7.0_75 源码为例,不同 JDK 源码可能存在差异。首先,我们看以下部分JDK代码(本部分代码来自 Runtime 类中的 exec 方法):

public Process exec(String command, String[] envp, File dir)
        throws IOException {
        if (command.length() == 0)
            throw new IllegalArgumentException("Empty command");
        StringTokenizer st = new StringTokenizer(command);
        String[] cmdarray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++)
            cmdarray[i] = st.nextToken();
        return exec(cmdarray, envp, dir);
 

上述代码是将传入的命令根据空格进行分割,然后放入 cmdarray 数组中。这个数组内容是{"D:\Program,Files\MySQL\MySQL,Server,5.1\bin\mysqldUSB.exe",--defaults-file="D:\Program,Files\MySQL\MySQL,Server,5.1\my.ini"}。而后,我们继续阅读代码在 java.lang.ProcessImpl 类中找到了我们需要的答案。

private static boolean needsEscaping(int verificationType, String arg) {
        boolean argIsQuoted = isQuoted(
            (verificationType == VERIFICATION_CMD_BAT),
            arg, "Argument has embedded quote, use the explicit CMD.EXE call.");
        if (!argIsQuoted) {
            char testEscape[] = ESCAPE_VERIFICATION[verificationType];
            for (int i = 0; i < testEscape.length; ++i) {
                if (arg.indexOf(testEscape[i]) >= 0) {
                    return true;
        return false;
 

arg 是前面的 cmdarray 数组元素,这里的 verificationType 值是2,取出的 ESCAPE_VERIFICATION 数组内容是 {' ', '\t'} 。所以这段代码是判断 arg 中是否含有 空格或者制表符。接着我们来看调用这个函数的地方:

private static String createCommandLine(int verificationType,
                                     final String executablePath,
                                     final String cmd[])
        StringBuilder cmdbuf = new StringBuilder(80);
        cmdbuf.append(executablePath);
        for (int i = 1; i < cmd.length; ++i) {
            cmdbuf.append(' ');
            String s = cmd[i];
            if (needsEscaping(verificationType, s)) {
                cmdbuf.append('"').append(s);
                // The code protects the [java.exe] and console command line
                // parser, that interprets the [\"] combination as an escape
                // sequence for the ["] char.
                //     http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
                // If the argument is an FS path, doubling of the tail [\]
                // char is not a problem for non-console applications.
                // The [\"] sequence is not an escape sequence for the [cmd.exe]
                // command line parser. The case of the [""] tail escape
                // sequence could not be realized due to the argument validation
                // procedure.
                if ((verificationType != VERIFICATION_CMD_BAT) && s.endsWith("\\")) {
                    cmdbuf.append('\\');
                cmdbuf.append('"');
            } else {
                cmdbuf.append(s);
        return cmdbuf.toString();
 

从上述代码可以看出: cmdarray 数组中的元素若含有空格或者制表符则在这个数组元素的前后加上引号"",然后将每一个元素重新拼接。再来看我们传入的命令 C:\Program Files\MySQL\MySQL Server 5.1\bin\mysqld --defaults-file=C:\Program Files\MySQL\MySQL Server 5.1\bin\my.ini 。居然在路径中带有空格,所以在执行代码的时候由于带有空格导致实际传入mysqld的路径是错的,,真实传入的路径就变成了C:\ProgramFiles\MySQL\MySQLServer5.1\bin,那么肯定就无法启动mysql咯。

那么,我们要如何做才能启动mysql呢? 从API中我们可以看到Runtime的exec方法重载了好几个。其中有这么一个方法: public Process exec(String command, String[] envp, File dir) 。参数分别代表的是:

  1. command : 要执行的命令
  2. envp : 指定需要的环境变量
  3. dir : 命令执行的子目录

代码实现如下:

@Test
public void cmdTest2() {
    String sDir = "C:\\Program Files\\MySQL\\MySQL Server 5.1\\bin";
    String sStartMysql =  "mysqldUSB --defaults-file=../my.ini";
    try {
		Runtime.getRuntime().exec(new String[]{"cmd","/c",sStartMysql}, null, new File(sDir));
	} catch (IOException e) {
		System.out.println("command :"+cmd+" "+e.getMessage());
 

Java执行多条命令

将 cmd 脚本文件写入代码中,不可避免的会遇到一次执行多条命令的情况。但是 exec 方法一次只能执行一条命令。

利用脚本本身的特点

我们可以利用脚本本身的一些特点来实现执行多条命令。(这里以 cmd 命令为例) 主要利用 & 和 && 字符的特性。它们的区别如下:

  1. & 连接多条命令,且不关系前一条是否执行成功;
  2. && 连接多条命令,前一条命令执行失败后不再执行后续命令。

利用流实现

执行 exec 方法后会返回一个 Process 对象。 Process 类中有一个方法是 getOutputStream ,用于获取子进程的输出流。输出流被传送给由该 Process 对象表示的进程的标准输入流。根据这一特性,我们可以执行多条命令。

@Test
public void cmdTest3() {
    //本次代码省略具体的命令集 sCommands 内容
    DataOutputStream out=null;
	try {
		Process process = Runtime.getRuntime().exec("cmd");
		out = new DataOutputStream(process.getOutputStream());
		for(String sCommand: sCommands) {
			out.writeBytes(sCommand);
			out.writeBytes("\n");
			out.flush();
		out.writeBytes("exit\n");
		out.flush();
	} catch (IOException e) {
		System.err.println(e.getMessage());
	} finally {
		try {
			if(out != null) out.close();
		} catch (IOException e) {
			System.err.println(e.getMessage());
 

通过流的方式我们将每一条命令传送过去执行。要注意的是执行结束后需要执行 exit 命令用于关闭 cmd 。

通过这次的教训深深感受到需要加深对 JDK 源码的理解,一段看似合理的启动 Mysql 代码却由于对 JDK 源码的不熟悉而导致执行失败,并花费了比较长的时间排查问题。同时,还需要多多查阅 API ,一些不常用的 API 可能就会帮助我们解决一个困扰已久或者实行复杂的问题。

转载于:https://my.oschina.net/ptczy/blog/1631347

最近在做一个项目,经常需要写 bat 脚本,后来由于需要把bat脚本的内容内嵌代码中,从而引发了一些问题。执行启动Mysql命令问题:在 Java 中执行 C:\Program Files\MySQL\MySQL Server 5.1\bin\mysqld --defaults-file=...
在使用Process执行shell命令时,如果使用APIexitValue()来判断shell是正常结束(即exit 0)还是异常结束,则可能引发异常:java.lang.IllegalThreadStateException: process hasn’t exited 因为exitValue方法没有阻塞,如果执行shell命令的进程还没有结束,则会引发异常,源码实现如下: public synchronized int exitValue() { if (!hasExited) {
最近在Mac下面使用Intellij Idea部署应用出现一个错误 [2017-01-08 11:13:45,148] Artifact Lantek:war exploded: Server is not connected. Deploy is not available. 错误: 代理抛出异常错误: java.net.MalformedURLException: Local h
将JDK升级到7u21后,一个应用的内置Tomcat启动不了,报出 Executable name has embedded quote, split the arguments 的错误。 经过查询发现 jdk 7u21 和 jdk 6u 45的改变了Runtime.exec方法实现。对含有空格的命令会有影响。 Changes to Runtime.exec On Windo
java: java.lang.IllegalArgumentException: MALFORMED idea运行tomcat时出现这个问题 这个地方右侧的Classpath中不能出现含有中文名的jar包。 一般来讲这些jar包或者maven库的导入啥的,路径中一般都不可以使用含有中文名的。 带尺寸的图片: 居中的图片: 居中并且带尺寸的图片: 当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。 如何插入一段漂亮的代码片 去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代
最近添加代码审查工具 findbugs,本来项目正常,但是安装后,重新编译项目报 Malformed argument has embedded quote 查找后按以下方法可以编译正常 在Help 中选择 Edit Custom VM Options 配置内容里添加一行 -Djdk.lang.Process.allowAmbiguousCommands=true 之后重启 IDEA,问题就能解决.
1、执行命令的方法 Runtime.exec("cmd",...)方法:详见Java Runtime类源码分析(开发“Java命令执行器”前期准备)_江南煮酒的博客-CSDN博客 ProcessBuilder.command("cmd",...).start():详见Java ProcessBuilder类源码分析(开发“Java命令执行器”前期准备)_江南煮酒的博客-CSDN博客
可以使用Java中的`Runtime`类来调用Windows命令。以下是一个简单的示例,该示例使用`Runtime.exec()`方法调用Windows命令: ```java import java.io.IOException; public class CommandExecutor { public static void main(String[] args) { try { // 调用ipconfig命令 Process p = Runtime.getRuntime().exec("ipconfig"); // 获取命令执行结果并输出 java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream())); String line; while ((line = in.readLine()) != null) { System.out.println(line); in.close(); } catch (IOException e) { e.printStackTrace(); 在这个例子中,我们调用了`ipconfig`命令并输出了命令的结果。你也可以使用其他Windows命令。请注意,你需要在Java程序中捕获`IOException`异常,因为在执行命令时可能会发生错误