Java中可以进行命令执行的类有Runtime
、ProcessBuilder
、ProcessImpl
等。
ProcessBuilder类 ProcessBuilder
类用于创建操作系统进程。每个ProcessBuilder
实例管理一个进程属性集,其start
方法利用这些属性来创建进程。
ProcessBuilder
的构造方法为ProcessBuilder(String... command)
,第一个参数为要执行的程序,后面的参数会被作为执行程序的参数。
如下是其基本用法:
1 2 3 4 5 6 7 8 InputStream in = new ProcessBuilder ("ipconfig" , "/all" ).start().getInputStream();ByteArrayOutputStream out = new ByteArrayOutputStream ();byte [] tmp = new byte [1024 ];int len = 0 ;while ((len=in.read(tmp)) > 0 ){ out.write(tmp, 0 , len); } System.out.println(out.toString());
Runtime类 每个Java程序都一个单例的Runtime实例,其允许程序能够访问程序运行时的环境。Runtime实例可以通过Runtime.getRuntime()
来获取。
Runtime有exec方法,可以执行系统命令,其有多个重载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public Process exec (String command) throws IOException { return exec(command, null , null ); } public Process exec (String command, String[] envp) throws IOException { return exec(command, envp, null ); } public Process exec (String command, String[] envp, File dir) throws IOException { if (command.isEmpty()) 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); } public Process exec (String cmdarray[]) throws IOException { return exec(cmdarray, null , null ); } public Process exec (String[] cmdarray, String[] envp) throws IOException { return exec(cmdarray, envp, null ); } public Process exec (String[] cmdarray, String[] envp, File dir) throws IOException { return new ProcessBuilder (cmdarray) .environment(envp) .directory(dir) .start(); }
可见,所有的exec方法最终都会调用到public Process exec(String[] cmdarray, String[] envp, File dir)
这个方法,并且底层是调用的ProcessBuilder
的start
方法。
cmdarray
: 要执行的命令数组,第一个是要执行的程序,之后的被作为参数传入envp
: 指明环境变量dir
: 指明工作路径
对于exec(String command)
,其参数就是要执行的命令,例如:
1 2 3 4 5 6 7 8 InputStream in = Runtime.getRuntime().exec("whoami" ).getInputStream();ByteArrayOutputStream out = new ByteArrayOutputStream ();byte [] tmp = new byte [1024 ];int len = 0 ;while ((len=in.read(tmp)) > 0 ){ out.write(tmp, 0 , len); } System.out.println(bao.toString());
ProcessImpl类 ProcessBuilder.start()
正是使用ProcessImpl
类来创建新的进程的。
ProcessImpl
类有start
的静态方法,用于创建新的进程。由于ProcessImpl
类不是publi
c的,因此无法直接在java.lang
包外调用,只能通过反射调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 String[] cmds = new String []{"whoami" }; byte [] bs = new byte [2048 ];int readSize = 0 ;ByteArrayOutputStream outputStream = new ByteArrayOutputStream ();Class clazz = Class.forName("java.lang.ProcessImpl" );Method method = clazz.getDeclaredMethod("start" , String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean .class);method.setAccessible(true ); Process p = (Process) method.invoke(null , cmds, null , "." , null , true );InputStream inputStream = p.getInputStream();while ((readSize = inputStream.read(bs)) > 0 ) { outputStream.write(bs, 0 , readSize); } System.out.println(outputStream.toString());
ScriptEngineManager类代码执行 ScriptEngineManager
类用来实现Java和Js之间的调用,其全类名为javax.script.ScriptEngineManager
。
ScriptEngineManager
的getEngineByExtension
方法返回一个ScriptEngine
接口的实现类,ScriptEngine
接口中有一个eval
方法,可以执行java代码
1 2 3 new ScriptEngineManager ().getEngineByExtension("js" ).eval("java.lang.Runtime.getRuntime().exec(\"calc\")" );new javax .script.ScriptEngineManager().getEngineByName("nashorn" ).eval("java.lang.Runtime.getRuntime().exec(\"calc\")" )
需要注意的是,这需要在有相应engine的环境中才能有效。
命令执行的一个问题 当执行例如whoami > a.txt
命令的时候,会发现执行不会成功。
对于Runtime
,原因是其参数会传到如下的方法中
1 2 3 4 5 6 7 8 9 10 public Process exec (String command, String[] envp, File dir) throws IOException { if (command.isEmpty()) 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); }
会将传入的字符串命令按照\t\n\r\f
和空格进行分割,分割为字符串数组后再调用最终的exec
方法。
经过调试即可看到whoami > a.txt
变成了["whoami", ">", "a.txt"]
,再传入ProcessBuilder
中,所以会出错。
解决方法
在window中,可以使用cmd /c
添加在命令之前,将后面的所有内容作为参数
1 2 3 4 5 Runtime.getRuntime().exec("cmd /c echo 111 > a.txt" ); new ProcessBuilder ("cmd" , "/c" , "echo 1223 > a.txt" ).start();
在linux中,则不能Runtime.getRuntime().exec("/bin/sh -c echo 111 > a.txt");
这样调用,原因是sh -c
需要一个字符串作为参数来执行
在linux中,只能进行如下调用
1 2 3 4 Runtime.getRuntime().exec(new String []{"/bin/sh" , "-c" , "echo 1 > a.txt" }) new ProcessBuilder ("/bin/sh" , "-c" , "echo 1 > a.txt" ).start();
除此之外,linux还可以使用base64编码/bin/bash -c {echo,d2hvYW1p}|{base64,-d}|{bash,-i}