`
jiangshuiy
  • 浏览: 340006 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java中Runtime.exec的一些事

 
阅读更多

0 预备知识

1 不正确的调用exitValue

2不正确的调用waitFor

3 一种可接受的调用方式

4 调用认为是可执行程序的时候容易发生的错误

5 window执行的良好示例

6 不良好的重定向命令输出

7 良好的重定向输出示例

8 总结

9 问答

 

0 预备知识

Runtime类是一个与JVM运行时环境有关的Singleton类,有以下几个值得注意的地方:

0.1 Runtime.getRuntime()可以取得当前JVM的运行时环境,这也是在Java中唯一得到运行时环境的方法。

0.2 Runtime上其他大部分的方法都是实例方法,也就是说每次进行运行时调用时都要用到getRuntime方法。

0.3 Runtime中的exit方法是退出当前JVM的方法,估计也是唯一的。System类中的exit实际上也是通过调用Runtime.exit()来退出JVM的。

JavaRuntime返回值的一般规则,0代表正常退出,非0代表异常中止,这只是Java的规则,在各个操作系统中总会发生一些小的混淆。

0.4 Runtime.addShutdownHook()方法可以注册一个hookJVM执行shutdown的过程中,方法的参数只要是一个初始化过但是没有执行的Thread实例就可以。(注意,Java中的Thread都是执行过了就不值钱的哦)

0.5说到addShutdownHook这个方法就要说一下JVM运行环境是在什么情况下shutdown或者abort的。

Shutdown:当最后一个非精灵进程退出或者收到了一个用户中断信号、用户登出、系统shutdownRuntimeexit方法被调用时JVM会启动shutdown的过程,在这个过程开始后,他会并行启动所有登记的shutdown hook(注意是并行启动,这就需要线程安全和防止死锁)。当shutdown过程启动后,只有通过调用halt方法才能中止shutdown的过程并退出JVM

Abort abort退出时JVM就是停止运行但并不一定进行shutdown。这只有JVM在遇到SIGKILL信号或者windows中止进程的信号、本地方法发生类似于访问非法地址一类的内部错误时会出现。这种情况下并不能保证shutdown hook是否被执行。

0.6 Runtime.exec()方法的所有重载。这里要注意的是:

public Process exec(String[] cmdarray, String[] envp, File dir)

这个方法中cmdArray是一个执行的命令和参数的字符串数组,数组的第一个元素是要执行的命令往后依次都是命令的参数,envp中是name=value形式的环境变量设置,如果子进程要继承当前进程的环境时是null

 

不正确的调用exitValue

public class BadExecJavac {
    public static void main(String args[]) {
        try {
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec("java");
            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 BadExecJavac.main(BadExecJavac.java:26)
  

错误分析:

主要问题就是错误的调用了exitValue来取得外部命令的返回值。因为exitValue方法是非阻塞的,在调用这个方法时外部命令并没有返回所以引起异常。阻塞形式的方法是waitFor,它会一直等待外部命令执行完毕,然后返回执行的结果。

当你在一个Process上调用waitFor方法时,当前线程是阻塞的,如果外部命令无法执行结束,那么你的线程就会一直阻塞下去,这种意外会影响我们程序的执行。所以在我们不能判断外部命令什么时候执行完毕而我们的程序还需要继续执行的情况下,我们就应该循环的使用exitValue来取得外部命令的返回状态,并在外部命令返回时作出相应的处理。

 

2不正确的调用waitFor

 

public class BadExecJavac2 {
    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();
        }
    }
}

       不幸的是,这个程序也无法执行完成,它没有输出但却一直悬在那里!这是为什么那?

 

JDK文档中的解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入和从标准输入快速的读入都有可能造成子进程的锁,甚至死锁。

JDK仅仅说明为什么问题会发生,却并没有说明这个问题怎么解决。

解决方法就是:执行完外部命令后我们要控制好Process的所有输入和输出(视情况而定),//在这个例子里边因为调用的是Javac,而他在没有参数的情况下会将提示信息输出到标准出错,所以在下面的程序中我们要对此进行处理。

 

3 一种可接受的调用方式

public class MediocreExecJavac {
    public static void main(String args[]) {
        try {
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec("javac");
            InputStream stderr = proc.getErrorStream();
            InputStreamReader isr = new InputStreamReader(stderr);
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            System.out.println("<error></error>");
            while ((line = br.readLine()) != null)
                System.out.println(line);
            System.out.println("");
            int exitVal = proc.waitFor();
            System.out.println("Process exitValue: " + exitVal);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

输出:

<error></error>
Usage: javac <options></options> <source files=""></source>

...

Process exitValue: 2

  结果分析:

 

出来了结果。

为了处理好外部命令大量输出的情况,你要确保你的程序处理好外部命令所需要的输入或者输出。

 

4 调用认为是可执行程序的时候容易发生的错误

public class BadExecWinDir {
    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></output>");
            while ((line = br.readLine()) != null)
                System.out.println(line);
            System.out.println("");
            int exitVal = proc.waitFor();
            System.out.println("Process exitValue: " + exitVal);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

 

输出:

java.io.IOException: Cannot run program "dir": CreateProcess error=2, ...
	at java.lang.ProcessBuilder.start(ProcessBuilder.java:460)
	at java.lang.Runtime.exec(Runtime.java:593)
	at java.lang.Runtime.exec(Runtime.java:431)
	at java.lang.Runtime.exec(Runtime.java:328)
	at BadExecWinDir.main(BadExecWinDir.java:29)

  原因分析:

 

因为dir命令是由windows中的解释器解释的,直接执行dir时无法找到dir.exe这个命令,所以会出现文件未找到这个2的错误。如果我们要执行这样的命令,就要先根据操作系统的不同执行不同的解释程序command.com 或者cmd.exe

 

5 window执行的良好示例

public 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();
        }
    }
}
public class GoodWindowsExec {
    public static void main(String args[]) {
        if (args.length < 1) {
            System.out.println("USAGE: java GoodWindowsExec <cmd></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();
            // any error???
            int exitVal = proc.waitFor();
            System.out.println("ExitValue: " + exitVal);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

 

输出:

 

ExitValue: 0

 

原因分析:

就是cmd.exe /C +一个windows中注册了后缀的文档名,windows会自动地调用相关的程序来打开这个文档。

不要假设你执行的程序是可执行的程序,要清楚自己的程序是单独可执行的还是被解释的, 这里还有一点,就是得到process的输出的方式是getInputStream,这是因为我们要从Java 程序的角度来看,外部程序的输出对于Java来说就是输入,反之亦然。

 

 

6 不良好的重定向命令输出

错误的认为exec方法会接受所有你在命令行或者Shell中输入并接受的字符串。这些错误主要出现在命令作为参数的情况下,程序员错误的将所有命令行中可以输入的参数命令加入到exec中。下面的例子中就是一个程序员想重定向一个命令的输出。

public 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();
        }
    }
}

 

程序员的本意是将Hello World这个输入重订向到一个文本文件中,但是这个文件并没有生成,jecho仅仅是将命令行中的参数输出到标准输出中,用户觉得可以像dos中重定向一样将输出重定向到一个文件中,但这并不能实现,用户错误的将exec认为是一个shell解释器,但它并不是,如果你想将一个程序的输出重定向到其他的程序中,你必须用程序来实现他。可用java.io中的包。

 

7 良好的重定向输出示例

public 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();
        }
    }
}
public class GoodWinRedirect {
    public static void main(String args[]) {
        args = new String[1];
        args[0]="g:\\out.txt";
        if (args.length < 1) {
            System.out.println("USAGE java GoodWinRedirect <outputfile></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();
        }
    }
}

 

 

总结

总结了几条规则,防止我们在进行Runtime.exec()调用时出现错误。

  • 在一个外部进程执行完之前你不能得到他的退出状态
  • 在你的外部程序开始执行的时候你必须马上控制输入、输出、出错这些流。
  • 你必须用Runtime.exec()去执行程序
  • 你不能象命令行一样使用Runtime.exec()

 

问答

问:为什么Runtime.exec("ls")没有任何输出? 

答:调用Runtime.exec方法将产生一个本地的进程,并返回一个Process子类的实例,该实例可用于控制进程或取得进程的相关信息。

由于调用Runtime.exec方法所创建的子进程没有自己的终端或控制台,因此该子进程的标准IO(stdinstdoustderr)都通过Process.getOutputStream()Process.getInputStream()Process.getErrorStream()方法重定向给它的父进程了。用户需要用这些stream来向子进程输入数据或获取子进程的输出。所以正确执行Runtime.exec("ls")的例程如下

        try {
            Process process = Runtime.getRuntime().exec(command);
            InputStreamReader ir = new InputStreamReader(process.getInputStream());
            LineNumberReader input = new LineNumberReader(ir);
            String line;
            while ((line = input.readLine()) != null)
                System.out.println(line);
        } catch (java.io.IOException e) {
            System.err.println("IOException " + e.getMessage());
        }
分享到:
评论
4 楼 levin_china 2016-07-21  
jianyan163qq 写道
好文章!
我现在遇到一个调用的问题:
  我在Java程序里调用一个ffmpeg.exe的程序去生成视频的缩略图,然后在这个缩略图上在加上一个png的小图片, 这样的结果就是这个最终的缩略图看上去就像是可以点击的视频一样.
但是结果是, 直接使用main方法运行这个方法的时候, 一切正常, 而一旦在项目里调用, 这个第一步的视频缩略图并不会马上生成, 而是等了很久之后才生成. 而我在第一步生成之后去判断这个文件存不存在, 如果存在才会去进行第二部, 结果是都没有生成. 不知道这是什么问题, 我猜测可能是因为jvm并没有立即去执行这个进程导致.  暂时还没有解决方法. 你能给些建议吗?

ffmpeg.exe 有个参数是否立即生成缩略图。具体你查查api
3 楼 wenlongsust 2015-07-06  
exec可能存在注入漏洞,如何控制安全问题啊?
2 楼 jiangshuiy 2014-04-29  
jianyan163qq 写道
好文章!
我现在遇到一个调用的问题:
  我在Java程序里调用一个ffmpeg.exe的程序去生成视频的缩略图,然后在这个缩略图上在加上一个png的小图片, 这样的结果就是这个最终的缩略图看上去就像是可以点击的视频一样.
但是结果是, 直接使用main方法运行这个方法的时候, 一切正常, 而一旦在项目里调用, 这个第一步的视频缩略图并不会马上生成, 而是等了很久之后才生成. 而我在第一步生成之后去判断这个文件存不存在, 如果存在才会去进行第二部, 结果是都没有生成. 不知道这是什么问题, 我猜测可能是因为jvm并没有立即去执行这个进程导致.  暂时还没有解决方法. 你能给些建议吗?

你这个等了很久生成的情况时中间有做其它操作么?
建议断点看看方法是否正常调用到了
1 楼 jianyan163qq 2014-04-24  
好文章!
我现在遇到一个调用的问题:
  我在Java程序里调用一个ffmpeg.exe的程序去生成视频的缩略图,然后在这个缩略图上在加上一个png的小图片, 这样的结果就是这个最终的缩略图看上去就像是可以点击的视频一样.
但是结果是, 直接使用main方法运行这个方法的时候, 一切正常, 而一旦在项目里调用, 这个第一步的视频缩略图并不会马上生成, 而是等了很久之后才生成. 而我在第一步生成之后去判断这个文件存不存在, 如果存在才会去进行第二部, 结果是都没有生成. 不知道这是什么问题, 我猜测可能是因为jvm并没有立即去执行这个进程导致.  暂时还没有解决方法. 你能给些建议吗?

相关推荐

    Java使用Runtime.exec()给Windows命令提示符做了个外壳,真的很山寨!

    在Java编程中,`Runtime.exec()`方法是一个非常实用的功能,它允许我们执行操作系统级别的命令。这篇博客"Java使用Runtime.exec()给Windows命令提示符做了个外壳,真的很山寨!"探讨了如何利用`Runtime.exec()`来...

    java执行可执行文件,Runtime.exec、ProcessBuilder、commons-exec

    在Java编程中,经常需要执行一些外部的可执行文件或者脚本(如bat或shell脚本)。本文将详细介绍三种常用的方法:`Runtime.exec()`、`ProcessBuilder`以及`commons-exec`库。 #### 1. 使用`Runtime.exec()` `...

    java.lang.Runtime.exec&#40;&#41; Payload知识点详解

    java.lang.Runtime.exec() 方法是 Java 语言中用于执行操作系统命令的方法,但需要注意一些重要的知识点,以避免出现问题。通过使用 Base64 编码、重定向和管道字符,可以避免命令参数中不能使用空格的问题,并确保...

    使用runtime实现linux命令行或shell脚本多次调用

    在Java编程中,有时我们需要与操作系统进行交互,执行一些系统级别的任务,比如运行Linux命令或shell脚本。`Runtime`类是Java标准库提供的一种机制,允许我们在程序中执行操作系统命令。本文将深入探讨如何使用`...

    【IDEA】windows环境下IDEA java代码Runtime.getRuntime.exec中shell的执行环境的解决方案

    windows环境下IDEA java代码Runtime.getRuntime.exec中shell的执行环境的解决方案前言解决办法后记 前言 在使用IDEA本地开发监控守护线程的后台,我遇上了执行环境不兼容的问题,爆出各种“xxx不是内部或外部命令,...

    Runtime 执行bat

    在Java编程语言中,`Runtime`类是每个Java应用程序都有的一个实例,它提供了与运行时环境交互的方法。当我们需要在Java程序中执行系统命令,比如运行一个批处理脚本(.bat文件)时,`Runtime`类或者其扩展类`...

    深入研究java.lang.Runtime类.doc

    "深入研究java.lang....java.lang.Runtime 类提供了访问当前 Java 应用程序的 Runtime 环境的能力,并提供了一些有用的方法来控制 Java 虚拟机的状态和行为。但是,在使用这些方法时,需要注意安全性和兼容性问题。

    Java调用应用程序和Dos中的命令

    在Java中,`java.lang.Runtime`类提供了与当前运行时系统交互的方法,其中包括`exec()`方法,该方法允许我们启动一个新的进程并控制其输入和输出流。这对于需要与操作系统交互的任务非常有用。 #### 二、Runtime类...

    java中如何调cmd命令

    本文将详细介绍如何利用Java的`Runtime.getRuntime().exec()`方法来实现对CMD命令的调用,并通过具体的例子来帮助读者理解和掌握这一技术。 #### 1. 基本原理 `java.lang.Runtime`类提供了运行时环境的表示,通过...

    java调用本地浏览器的demo

    首先,`Runtime.getRuntime().exec()`方法是Java标准库中用于执行系统命令的常用方式。例如,如果你想在Windows环境下打开默认浏览器,你可以这样写: ```java String url = "http://www.example.com"; Runtime....

    Java调用Python的jar包

    首先,我们需要了解Java如何调用外部程序,这通常通过`java.lang.ProcessBuilder`类或`Runtime.getRuntime().exec()`方法来实现。这两个方法允许Java启动一个新的进程,并执行操作系统命令。因此,我们可以在Java中...

    基于java.lang.Runtime封装的用于简化java调用命令行的工具.zip

    Java语言在执行外部程序或与操作系统交互时,通常会用到`java.lang.Runtime`类。这个类提供了运行时环境的接口,...总的来说,基于`java.lang.Runtime`封装的工具是Java开发中的实用利器,值得在实际项目中广泛采用。

    java调用播放器

    `Runtime.exec()`方法用于在Java应用程序中执行一个外部命令或程序。它返回一个`Process`对象,该对象可以用来控制所启动的进程,并且能够获取其输入/输出流。 - **语法**: ```java public static Process exec...

    java调用windows命令

    在Java编程中,有时我们需要在程序中调用Windows系统的命令行操作,例如执行外部程序、系统命令或者进行文件操作。Java提供了Runtime类和Process类来实现这一功能。以下是如何使用Java调用Windows命令行的详细步骤和...

    AIUI使用.rar

    runtime.exec("cmd /c start " + url); } catch (IOException e) { e.printStackTrace(); } } /** * 鍦ㄥ欢杩熸寚瀹氱殑绉掓暟鍚庡叧鏈? * * @param sec * 鍏虫満寤惰繜锛屽崟浣嶏細绉? */ ...

    java修改文件属性

    这通常是通过`java.lang.Runtime`类的`exec()`方法执行外部命令来实现的。 1. **设置文件为只读**:使用`File.setReadOnly()`。 ```java File file = new File("path/to/your/file"); boolean success = file....

    java导出orcale数据库dmp文件

    首先,我们需要了解Java中与Oracle数据库交互的基本工具:JDBC(Java Database Connectivity)。JDBC是Java API,允许Java应用程序连接到各种数据库,包括Oracle。你需要在项目中添加Oracle JDBC驱动,如ojdbc.jar,...

    深入研究java.lang.Process类.doc

    Java.lang.Process 类是 Java 类库中的一个核心部分,它提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。下面将对 Process 类的概述、API 预览、创建 Process ...

Global site tag (gtag.js) - Google Analytics