`
cary1130
  • 浏览: 200372 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Runtime.exec()的使用

    博客分类:
  • java
阅读更多

那就首先说点Runtime类吧,他是一个与JVM运行时环境有关的类,这个类是Singleton的。我说几个自己觉得重要的地方。

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

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

3、Runtime中的exit方法是退出当前JVM的方法,估计也是唯一的一个吧,因为我看到System类中的exit实际上也是通过调用Runtime.exit()来退出JVM的,这里说明一下Java对Runtime返回值的一般规则(后边也提到了),0代表正常退出,非0代表异常中止,这只是Java的规则,在各个操作系统中总会发生一些小的混淆。


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

5、说到addShutdownHook这个方法就要说一下JVM运行环境是在什么情况下shutdown或者abort的。文档上是这样写的,当最后一个非精灵进程退出或者收到了一个用户中断信号、用户登出、系统shutdown、Runtime的exit方法被调用时JVM会启动shutdown的过程,在这个过程开始后,他会并行启动所有登记的shutdown hook(注意是并行启动,这就需要线程安全和防止死锁)。当shutdown过程启动后,只有通过调用halt方法才能中止shutdown的过程并退出JVM。

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


现在开始看这篇文章,呵呵。


首先讲的是Runtime.exec()方法的所有重载。这里要注意的有一点,就是public Process exec(String [] cmdArray, String [] envp);这个方法中cmdArray是一个执行的命令和参数的字符串数组,数组的第一个元素是要执行的命令往后依次都是命令的参数,envp我个人感觉应该和C中的execve中的环境变量是一样的,envp中使用的是name=value的方式。


<!---->1、 <!---->一个很糟糕的调用程序,代码如下,这个程序用exec调用了一个外部命令之后马上使用exitValue就对其返回值进行检查,让我们看看会出现什么问题。


import java.util.*;
import java.io.*;

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

A run of BadExecJavac produces:


E:classescomjavaworldjpitfallsarticle2>java BadExecJavac
java.lang.IllegalThreadStateException: process has not exited
at java.lang.Win32Process.exitValue(Native Method)
at BadExecJavac.main(BadExecJavac.java:13)


这里看原文就可以了解,这里主要的问题就是错误的调用了exitValue来取得外部命令的返回值(呵呵,这个错误我也曾经犯过),因为exitValue这个方法是不阻塞的,程序在调用这个方法时外部命令并没有返回所以造成了异常的出现,这里是由另外的方法来等待外部命令执行完毕的,就是waitFor方法,这个方法会一直阻塞直到外部命令执行结束,然后返回外部命令执行的结果,作者在这里一顿批评设计者的思路有问题,呵呵,反正我是无所谓阿,能用就可以拉。但是作者在这里有一个说明,就是exitValue也是有好多用途的。因为当你在一个Process上调用waitFor方法时,当前线程是阻塞的,如果外部命令无法执行结束,那么你的线程就会一直阻塞下去,这种意外会影响我们程序的执行。所以在我们不能判断外部命令什么时候执行完毕而我们的程序还需要继续执行的情况下,我们就应该循环的使用exitValue来取得外部命令的返回状态,并在外部命令返回时作出相应的处理。


2、对exitValue处改进了的程序

import java.util.*;
import java.io.*;

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,而他在没有参数的情况下会将提示信息输出到标准出错,所以在下面的程序中我们要对此进行处理。


import java.util.*;
import java.io.*;

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


程序的运行结果为

E:classescomjavaworldjpitfallsarticle2>java MediocreExecJavac
<error></error>
Usage: javac <options></options> <source files=""></source>

where <options></options> includes:
-g Generate all debugging info
-g:none Generate no debugging info
-g:{lines,vars,source} Generate only some debugging info
-O Optimize; may hinder debugging or enlarge class files
-nowarn Generate no warnings
-verbose Output messages about what the compiler is doing
-deprecation Output source locations where deprecated APIs are used
-classpath Specify where to find user class files
-sourcepath Specify where to find input source files
-bootclasspath Override location of bootstrap class files
-extdirs <dirs></dirs>Override location of installed extensions
-d <directory></directory>Specify where to place generated class files
-encoding <encoding></encoding>Specify character encoding used by source files
-target <release></release>Generate class files for specific VM version

Process exitValue: 2


哎,不管怎么说还是出来了结果,作者作了一下总结,就是说,为了处理好外部命令大量输出的情况,你要确保你的程序处理好外部命令所需要的输入或者输出。


下一个题目,当我们调用一个我们认为是可执行程序的时候容易发生的错误(今天晚上我刚刚犯这个错误,没事做这个练习时候发生的)

import java.util.*;
import java.io.*;

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

A run of BadExecWinDir produces:


E:classescomjavaworldjpitfallsarticle2>java BadExecWinDir
java.io.IOException: CreateProcess: dir error=2
at java.lang.Win32Process.create(Native Method)
at java.lang.Win32Process.<init></init>(Unknown Source)
at java.lang.Runtime.execInternal(Native Method)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at BadExecWinDir.main(BadExecWinDir.java:12)


说实在的,这个错误还真是让我摸不着头脑,我觉得在windows中返回2应该是没有找到这个文件的缘故,可能windows 2000中只有cmd命令,dir命令不是当前环境变量能够解释的吧。我也不知道了,慢慢往下看吧。

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

作者对上边的程序进行了修改

import java.util.*;
import java.io.*;

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

Running GoodWindowsExec with the dir command generates:


E:classescomjavaworldjpitfallsarticle2>java GoodWindowsExec "dir *.java"
Execing cmd.exe /C dir *.java
OUTPUT> Volume in drive E has no label.
OUTPUT> Volume Serial Number is 5C5F-0CC9
OUTPUT>
OUTPUT> Directory of E:classescomjavaworldjpitfallsarticle2
OUTPUT>
OUTPUT>10/23/00 09:01p 805 BadExecBrowser.java
OUTPUT>10/22/00 09:35a 770 BadExecBrowser1.java
OUTPUT>10/24/00 08:45p 488 BadExecJavac.java
OUTPUT>10/24/00 08:46p 519 BadExecJavac2.java
OUTPUT>10/24/00 09:13p 930 BadExecWinDir.java
OUTPUT>10/22/00 09:21a 2,282 BadURLPost.java
OUTPUT>10/22/00 09:20a 2,273 BadURLPost1.java
... (some output omitted for brevity)
OUTPUT>10/12/00 09:29p 151 SuperFrame.java
OUTPUT>10/24/00 09:23p 1,814 TestExec.java
OUTPUT>10/09/00 05:47p 23,543 TestStringReplace.java
OUTPUT>10/12/00 08:55p 228 TopLevel.java
OUTPUT> 22 File(s) 46,661 bytes
OUTPUT> 19,678,420,992 bytes free
ExitValue: 0

这里作者教了一个windows中很有用的方法,呵呵,至少我是不知道的,就是cmd.exe /C +一个windows中注册了后缀的文档名,windows会自动地调用相关的程序来打开这个文档,我试了一下,的确很好用,但是好像文件路径中有空格的话就有点问题,我加上引号也无法解决。

这里作者强调了一下,不要假设你执行的程序是可执行的程序,要清楚自己的程序是单独可执行的还是被解释的,本章的结束作者会介绍一个命令行工具来帮助我们分析。

这里还有一点,就是得到process的输出的方式是getInputStream,这是因为我们要从Java 程序的角度来看,外部程序的输出对于Java来说就是输入,反之亦然。


最后的一个漏洞的地方就是错误的认为exec方法会接受所有你在命令行或者Shell中输入并接受的字符串。这些错误主要出现在命令作为参数的情况下,程序员错误的将所有命令行中可以输入的参数命令加入到exec中(这段翻译的不好,凑合看吧)。下面的例子中就是一个程序员想重定向一个命令的输出。


import java.util.*;
import java.io.*;

// StreamGobbler omitted for brevity

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

Running BadWinRedirect produces:


E:classescomjavaworldjpitfallsarticle2>java BadWinRedirect
OUTPUT>'Hello World' > test.txt
ExitValue: 0

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


import java.util.*;
import java.io.*;

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[])
{
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();
}
}
}

Running GoodWinRedirect produces:


E:classescomjavaworldjpitfallsarticle2>java GoodWinRedirect test.txt
OUTPUT>'Hello World'
ExitValue: 0

这里就不多说了,看看就明白,紧接着作者给出了一个监测命令的小程序

import java.util.*;
import java.io.*;

// class StreamGobbler omitted for brevity

public class TestExec
{
public static void main(String args[])
{
if (args.length < 1)
{
System.out.println("USAGE: java TestExec "cmd"");
System.exit(1);
}

try
{
String cmd = args[0];
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(cmd);

// any error message?
StreamGobbler errorGobbler = new
StreamGobbler(proc.getErrorStream(), "ERR");

// any output?
StreamGobbler outputGobbler = new
StreamGobbler(proc.getInputStream(), "OUT");

// kick them off
errorGobbler.start();
outputGobbler.start();

// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal);
} catch (Throwable t)
{
t.printStackTrace();
}
}
}

对这个程序进行运行:
E:classescomjavaworldjpitfallsarticle2>java TestExec "e:javadocsindex.html"
java.io.IOException: CreateProcess: e:javadocsindex.html error=193
at java.lang.Win32Process.create(Native Method)
at java.lang.Win32Process.<init></init>(Unknown Source)
at java.lang.Runtime.execInternal(Native Method)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at TestExec.main(TestExec.java:45)

193在windows中是说这不是一个win32程序,这说明路径中找不到这个网页的关联程序,下面作者决定用一个绝对路径来试一下。

E:classescomjavaworldjpitfallsarticle2>java TestExec
"e:program filesnetscapeprogramnetscape.exe e:javadocsindex.html"
ExitValue: 0


好用了,这个我也试了一下,用的是IE。


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


<!---->1、 <!---->在一个外部进程执行完之前你不能得到他的退出状态

<!---->2、 <!---->在你的外部程序开始执行的时候你必须马上控制输入、输出、出错这些流。

<!---->3、 <!---->你必须用Runtime.exec()去执行程序

<!---->4、 <!---->你不能象命令行一样使用Runtime.exec()。

分享到:
评论
1 楼 cary1130 2007-05-11  
问:为什么Runtime.exec("ls")没有任何输出?

答:调用Runtime.exec方法将产生一个本地的进程,并返回一个Process子类的实例,该实例可用于控制进程或取得进程的相关信息。由于调用Runtime.exec方法所创建的子进程没有自己的终端或控制台,因此该子进程的标准IO(如stdin,stdou,stderr)都通过Process.getOutputStream(),Process.getInputStream(),Process.getErrorStream()方法重定向给它的父进程了。用户需要用这些stream来向子进程输入数据或获取子进程的输出。所以正确执行Runtime.exec("ls")的例程如下:

        try

        {

            process = Runtime.getRuntime().exec (command);

            InputStreamReader ir=newInputStreamReader(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());

        } 

相关推荐

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

    import org.apache.commons.exec.PumpStreamHandler; public class ExeRunUtil { public static void runWithCommonsExec(String... command) { CommandLine cmdLine = CommandLine.parse(command[0]); for (int...

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

    这篇博客"Java使用Runtime.exec()给Windows命令提示符做了个外壳,真的很山寨!"探讨了如何利用`Runtime.exec()`来创建一个简单的命令行界面,尽管这种方式可能并不完美,但不失为一种学习和实践的方法。 `Runtime....

    解决runtime.exec&#40;&#41;执行进程block死锁以及为waitFor设置超时

    完美解决runtime.exec&#40;&#41;执行进程block死锁以及为waitFor设置超时 不需要耗cpu的循环判断exitValue==0 开两个进程搞定

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

    在使用 java.lang.Runtime.exec() 方法时,命令参数中不能使用空格,否则将导致命令执行失败。这是因为 StringTokenizer 类将命令字符串按空格分隔,将参数解释为多个命令。这将导致命令执行失败,无法获取预期的...

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

    `Runtime.exec(String command)`方法用于执行单个命令,而如果需要执行包含多个命令的shell脚本,可以使用`Runtime.exec(String[] cmdArray)`,其中cmdArray是一个包含命令及其参数的字符串数组。 下面是一个简单的...

    Android中软件的静默安装

    1,申请root权限Runtime.getRuntime().exec&#40;"su"&#41;; 2,通过数据输出流DataOutputStream写入pm install命令; 3,最后获取Process进程的返回值int i = process.waitFor();,如果i=0,则表明已获取root权限。

    Runtime 执行bat

    下面我们将深入探讨如何使用`Runtime`执行bat脚本,并了解相关知识点。 1. **`Runtime.getRuntime()`方法** `Runtime.getRuntime()`是获取当前Java虚拟机的`Runtime`实例。这个静态方法让Java应用程序能够与操作...

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

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

    System.Runtime.InteropServices.COMException的解决方法

    完美解决“换另一台电脑上用VS2008继续开发web项目时出现 “System.Runtime.InteropServices.COMException”,然后是加载不了项目。” 只需要打开项目配置文件*.csproj,将&lt;UseIIS&gt;True&lt;/UseIIS&gt; 改为 False,然后...

    AIUI使用.rar

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

    android截屏

    这里不是通过view来截图,也不是通过底层的framebuffer实现截图,而是采用另外一种方法实现截图,通过Runtime.getRuntime().exec()来实现,并保存在sdcard上,代码很简单。

    android读取/显示logcat信息

    这通常涉及使用`Runtime.getRuntime().exec()`方法来执行shell命令。 4. **解析和显示日志**:将`adb logcat`的输出解析成日志条目,然后在界面上展示。每个条目包含时间戳、优先级、标签和消息。你可以创建一个...

    java中如何调cmd命令

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

    java调用本地浏览器的demo

    总结一下,Java调用本地浏览器的关键在于使用`Runtime.exec()`或`ProcessBuilder`执行操作系统级别的命令。具体命令取决于操作系统的类型,如Windows的`rundll32`,Linux的`xdg-open`,以及Mac OS的`open`。通过`...

    Java调用Python的jar包

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

    java调用播放器

    ### Java调用播放器知识点详解 #### 一、概述 ...通过上述内容,我们可以了解到如何在Java中使用`Runtime.exec()`方法来调用播放器以及执行其他系统命令。这对于开发具有多媒体功能的应用程序非常有用。

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

    下面是一个简单的示例,展示如何使用`Runtime.exec()`来调用一个外部应用程序(假设为`notepad.exe`): ```java import java.io.IOException; public class CallApplication { public static void main(String[]...

    java调用windows命令

    - 使用`Runtime.getRuntime().exec()`方法创建一个子进程来执行指定的可执行程序。例如: ```java Process process = Runtime.getRuntime().exec(".\\p.exe"); ``` - `exec()`方法返回一个Process对象,可以...

Global site tag (gtag.js) - Google Analytics