Implementation for running OS commands by using Java [Chinese]

enabling-tech.com原创】

Java 语言本身提供了执行外部程序的机制,那就是java.lang.Runtime。改类自Java诞生就已经从在,这个可以从见JDK帮助文档的Since得到。随着Java的发展,该类也多次修订。http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Runtime.html

改类提供了6个重载的方法(截至目前1.5版本),我们把它拷过来,用作参考:

Process exec(String command)
Executes the specified string command in a separate process.

Process exec(String[] cmdarray)
Executes the specified command and arguments in a separate process.

Process exec(String[] cmdarray, String[] envp)
Executes the specified command and arguments in a separate process with the specified environment.

Process exec(String[] cmdarray, String[] envp, File dir)
Executes the specified command and arguments in a separate process with the specified environment and working directory.

Process exec(String command, String[] envp)
Executes the specified string command in a separate process with the specified environment.

Process exec(String command, String[] envp, File dir)
Executes the specified string command in a separate process with the specified environment and working directory

按照文档上说,这行exec,就会产生一个与操作系统相关的process,Java用Process与之相联系,就是在Process里面有个这个实际的Process的引用。类Process的说明如下:

The ProcessBuilder.start() and Runtime.exec methods create a native process and return an instance of a subclass of Process that can be used to control the process and obtain information about it.

就是说执行了exec,剩下的(输入,输出,等待结束,结束返回值等等)就交给Process具体的实现类来处理:

The class Process provides methods for performing input from the process, performing output to the process, waiting for the process to complete, checking the exit status of the process, and destroying (killing) the process.

网络上有很多介绍的文章,但是如果你看了还是不是很明白的话,其实最重要的还是回到JDK的帮助文档里来,在类Process的文档剩下的部分写的很明白:

The methods that create processes may not work well for special processes on certain native platforms, such as native windowing processes, daemon processes, Win16/DOS processes on Microsoft Windows, or shell scripts. The created subprocess does not have its own terminal or console. All its standard io (i.e. stdin, stdout, stderr) operations will be redirected to the parent process through three streams (getOutputStream(), getInputStream(), getErrorStream()). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

The subprocess is not killed when there are no more references to the Process object, but rather the subprocess continues executing asynchronously.

There is no requirement that a process represented by a Process object execute asynchronously or concurrently with respect to the Java process that owns the Process object.

简单的说,所有的标准IO操作都会转给父进程,父进程需要用那三种流给子进程。更糟的是标准输入输出流的缓冲啊,就是buffer,有些操作系统给的是有限的(废话),那么不能及时写输入和读输出给子进程就或许导致子进程被block或者甚至死锁!

明白了上边的几点,我们来看一段例程,就比较清楚了:

直接使用参考文献1中的例程:

public class BadExecJavac
{
public static void main(String args[])
{
try
{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(”javac”);
int exitVal = proc.exitValue();
System.out.println(”Process exitValue: ” + exitVal);
}
catch (Throwable t)
{
t.printStackTrace();
}
}
}

执行结果如下:

java.lang.IllegalThreadStateException: process has not exited
at java.lang.ProcessImpl.exitValue(Native Method)
at com.test.tools.test.book.Test.main(Test.java:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)

什么原因呢? 查查文档,原来是说子进程还未结束,如果call exitValue()的话,就会报错。

老方法,查查文档,Process有个waitFor() 方法:

waitFor
public abstract int waitFor()
                     throws InterruptedException
causes the current thread to wait, if necessary, until the process represented by this Process object has terminated. This method returns immediately if the subprocess has already terminated. If the subprocess has not yet terminated, the calling thread will be blocked until the subprocess exits.
Returns:
the exit value of the process. By convention, 0 indicates normal termination.
Throws:
InterruptedException - if the current thread is interrupted by another thread while it is waiting, then the wait is ended and an InterruptedException is thrown.

好,我们立即改进我们的程序,使用waitFor():

public class Test

{

public static void main(String args[])

{

try

{

Runtime rt = Runtime.getRuntime();

Process proc = rt.exec(”javac”);

int exitVal = proc.waitFor();

System.out.println(”Process exitValue: ” + exitVal);

}

catch (Throwable t)

{

t.printStackTrace();

}

}

}

编译,运行。。。结果。。。结果。。。好像死在那里了#¥@%@@

好惨。。。立刻反省一下哪里错了?原来前文有提:我们没有处理输入,输出流×可能会×导致死锁! Javac执行如上的话,会把结果输入到stderr流中去的,结果父进程没有处理(所有的外部流都导给父进程,见文档),子进程等父进程处理,父进程等子进程结束(waitFor()),典型的死锁情节。下面,我们的程序需要加上流的处理的部分!

import java.io.BufferedReader;

import java.io.InputStream;

import java.io.InputStreamReader;

public class Test

{

public static void main(String args[])

{

try

{

Runtime rt = Runtime.getRuntime();

Process proc = rt.exec(”javac”);
//prepare to receive input err stream directed to This Process to drain.

InputStream stderr = proc.getErrorStream(); 得到错误流

InputStreamReader isr = new InputStreamReader(stderr);

BufferedReader br = new BufferedReader(isr);

String line = null;

System.out.println(”<ERROR>”);

while ((line = br.readLine()) != null)

{

System.out.println(line);

}

System.out.println(”</ERROR>”);

int exitVal = proc.waitFor();

System.out.println(”Process exitValue: ” + exitVal);

}

catch (Throwable t)

{

t.printStackTrace();

}

}

}

运行结果如下:

<ERROR>

用法:javac <选项> <源文件>

其中,可能的选项包括:

-g                         生成所有调试信息

-g:none                    不生成任何调试信息

-g:{lines,vars,source}     只生成某些调试信息

-nowarn                    不生成任何警告

-verbose                   输出有关编译器正在执行的操作的消息

-deprecation               输出使用已过时的 API 的源位置

-classpath <路径>            指定查找用户类文件的位置

-cp <路径>                   指定查找用户类文件的位置

-sourcepath <路径>           指定查找输入源文件的位置

-bootclasspath <路径>        覆盖引导类文件的位置

-extdirs <目录>              覆盖安装的扩展目录的位置

-endorseddirs <目录>         覆盖签名的标准路径的位置

-d <目录>                    指定存放生成的类文件的位置

-encoding <编码>             指定源文件使用的字符编码

-source <版本>               提供与指定版本的源兼容性

-target <版本>               生成特定 VM 版本的类文件

-version                   版本信息

-help                      输出标准选项的提要

-X                         输出非标准选项的提要

-J<标志>                     直接将 <标志> 传递给运行时系统

</ERROR>

Process exitValue: 2

Process finished with exit code 0

因为我在IDE里运行的,所以你会看到退出码是0,就是这个java类运行是正常。但是对于javac在例程中运行code是2.不同系统不同意义,这里代表文件没有找到。一般来说,0代表正常,非0都代表错误。

这段程序对这个例子来说足够了,但是缺乏灵活和完美!最要命的是这个程序还没有处理标准输入和输出流。

在开始完美化之前,先说说一个常见的错误。或许你已经迫不及待的编写和运行如下程序,结果发现是有问题的:

public class Test

{

public static void main(String args[])

{

try

{

Runtime rt = Runtime.getRuntime();

     Process proc = rt.exec(”dir”);

InputStream stdin = proc.getInputStream();

InputStreamReader isr = new InputStreamReader(stdin);

BufferedReader br = new BufferedReader(isr);

String line = null;

System.out.println(”<OUTPUT>”);

while ((line = br.readLine()) != null)

{

System.out.println(line);

}

System.out.println(”</OUTPUT>”);

int exitVal = proc.waitFor();

System.out.println(”Process exitValue: ” + exitVal);

}

catch (Throwable t)

{

t.printStackTrace();

}

}

}

为什么不行呢?这是因为dir这个命令需要windows 命令解释器,而不是一个单独存在的命令!也就是说该程序无法找到dir.

现在就让我们开始振奋人心的编程吧!采用一个专门的类处理外部程序需要输出的流,对于我们来说就是输入流。采用线程执行,所以可以异步的处理多个流。

/**

*  gobbler

1. 雄火鸡

2. 狼吞虎咽的人

*/

class StreamGobbler extends Thread

{

InputStream is;

String type;

StreamGobbler(InputStream is, String type)

{

this.is = is;

this.type = type;

}

public void run()

{

try

{

InputStreamReader isr = new InputStreamReader(is);

BufferedReader br = new BufferedReader(isr);

String line = null;

while ((line = br.readLine()) != null)

{

System.out.println(type + “>” + line);

}

}

catch (IOException ioe)

{

ioe.printStackTrace();

}

}

}

class GoodWindowsExec

{

public static void main(String args[])

{

if (args.length < 1)

{

System.out.println(”USAGE: java GoodWindowsExec <cmd>”);

System.exit(1);

}

try

{

String osName = System.getProperty(”os.name”);

String[] cmd = new String[3];

if (osName.equals(”Windows NT”))

{

cmd[0] = “cmd.exe”;

cmd[1] = “/C”;

cmd[2] = args[0];

}

else if (osName.equals(”Windows 95″))

{

cmd[0] = “command.com”;

cmd[1] = “/C”;

cmd[2] = args[0];

}

Runtime rt = Runtime.getRuntime();

System.out.println(”Execing ” + cmd[0] + ” ” + cmd[1]

+ ” ” + cmd[2]);

Process proc = rt.exec(cmd);

// any error message?

StreamGobbler errorGobbler = new

                    StreamGobbler(proc.getErrorStream(), “ERROR”);

            // any output?

StreamGobbler outputGobbler = new

                    StreamGobbler(proc.getInputStream(), “OUTPUT”);

            // kick them off

errorGobbler.start();

            outputGobbler.start();

// 还是惹不起那些进程,我等。

int exitVal = proc.waitFor();

System.out.println(”ExitValue: ” + exitVal);

}

catch (Throwable t)

{

t.printStackTrace();

}

}

}

用这个方式执行:

列目录,java GoodWindowsExec “dir *.java” 。

打开word文档,java GoodWindowsExec “yourdoc.doc”。其他就不赘述了。

另外,要记住,exec不是个命令解释器,它的作用只是运行命令。千万不要认为exec() method 好像一个shell interpreter。如果你那么认为,就会犯下面的错误:

class BadWinRedirect

{

public static void main(String args[])

{

try

{

Runtime rt = Runtime.getRuntime();

Process proc = rt.exec(”java jecho ‘Hello World’ > test.txt”);

// any error message?

StreamGobbler errorGobbler = new

StreamGobbler(proc.getErrorStream(), “ERROR”);

// any output?

StreamGobbler outputGobbler = new

StreamGobbler(proc.getInputStream(), “OUTPUT”);

// kick them off

errorGobbler.start();

outputGobbler.start();

// any error???

int exitVal = proc.waitFor();

System.out.println(”ExitValue: ” + exitVal);

}

catch (Throwable t)

{

t.printStackTrace();

}

}

}

该程序本意是想执行jecho ‘Hello World‘,然后把执行结果存入test.txt。Sorry,错了。你必须用编程的方法来实现。还记得我们前面说过子进程的输出流么,把子进程的输出流===》得到,然后存入test.txt就可以实现了。

有点晕?呵呵。我们里一下思路。该例程运行程序(java jecho ‘Hello World’),产生个子进程,子进程产生输出流。子进程如前所述没有地方释放,需要父进程排泄。所以,对父进程来说,就是得到proc.getInputStream(), 就是把子进程要输出的东西得到。

这样还需要改进一下我们(吃流者)这个类。SteamBobbler.:) 如下:

import java.io.BufferedReader;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.io.PrintWriter;

class StreamGobbler extends Thread

{

InputStream is;

String type;

OutputStream os;

StreamGobbler(InputStream is, String type)

{

this(is, type, null);

}

StreamGobbler(InputStream is, String type, OutputStream redirect)

{

this.is = is;

this.type = type;

this.os = redirect;

}

public void run()

{

try

{

       PrintWriter pw = null;

if (os != null)

            {

                pw = new PrintWriter(os);

            }

InputStreamReader isr = new InputStreamReader(is);

BufferedReader br = new BufferedReader(isr);

String line = null;

while ((line = br.readLine()) != null)

{

if (pw != null)

{

          pw.println(line);

}

System.out.println(type + “>” + line);

}

if (pw != null)

{

              pw.flush();

}

}

catch (IOException ioe)

{

ioe.printStackTrace();

}

}

}

class GoodWinRedirect

{

public static void main(String args[])

{

if (args.length < 1)

{

System.out.println(”USAGE java GoodWinRedirect <outputfile>”);

System.exit(1);

}

try

{

FileOutputStream fos = new FileOutputStream(args[0]);

Runtime rt = Runtime.getRuntime();

Process proc = rt.exec(”java jecho ‘Hello World’”);
// any error message?

StreamGobbler errorGobbler = new

StreamGobbler(proc.getErrorStream(), “ERROR”);

// any output?

StreamGobbler outputGobbler = new

StreamGobbler(proc.getInputStream(), “OUTPUT”, fos);

// kick them off

errorGobbler.start();

outputGobbler.start();

// any error???

int exitVal = proc.waitFor();

System.out.println(”ExitValue: ” + exitVal);

fos.flush();

fos.close();

}

catch (Throwable t)

{

t.printStackTrace();

}

}

}

OK,大功告成,基本上成功。

小结如下:

  1. You cannot obtain an exit status from an external process until it has exited
  2. You must immediately handle the input, output, and error streams from your spawned external process
  3. You must use Runtime.exec() to execute programs
  4. You cannot use Runtime.exec() like a command line

本文完。下期谈谈如何使用目录,环境变量等高级话题。

参考文档:

  1. http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html
相关帖子:
  • No Related Posts
  • micas Jun 13th 2008 02:00 pm JavaBasic, Enabling-tech No Comments yet Trackback URI Comments RSS

    Leave a Reply

    You must be logged in to post a comment.