java start打开cmd窗口并停住_浅析Java命令执行
在使用java.lang.Runtime#exec()執行命令時,為何有時候命令前綴需要加cmd /c或者bash -c?今天就來一探究竟!
Java執行命令的3種方法
首先了解下在Java中執行命令的方法:
常用的是 java.lang.Runtime#exec()和 java.lang.ProcessBuilder#start(),除此之外,還有更為底層的java.lang.ProcessImpl#start(),他們的調用關系如下圖所示:
其中,ProcessImpl類是Process抽象類的具體實現,且該類的構造函數使用private修飾,所以無法在java.lang包外直接調用,只能通過反射調用ProcessImpl#start()方法執行命令。
這3種執行方法如下:
java.lang.Runtime
public static String RuntimeTest() throws Exception { InputStream ins = Runtime.getRuntime().exec("whoami").getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] bytes = new byte[1024];int size;while((size = ins.read(bytes)) > 0) bos.write(bytes,0,size);return bos.toString();}java.lang.ProcessBuilder
public static String ProcessTest() throws Exception { String[] cmds = {"cmd","/c","whoami"}; InputStream ins = new ProcessBuilder(cmds).start().getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] bytes = new byte[1024];int size;while((size = ins.read(bytes)) > 0) bos.write(bytes,0,size);return bos.toString();}
java.lang.ProcessImpl
public static String ProcessImplTest() throws Exception { String[] cmds = {"whoami"}; Class clazz = Class.forName("java.lang.ProcessImpl"); Method method = clazz.getDeclaredMethod("start", new String[]{}.getClass(),Map.class,String.class,ProcessBuilder.Redirect[].class,boolean.class); method.setAccessible(true); InputStream ins = ((Process) method.invoke(null,cmds,null,".",null,true)).getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] bytes = new byte[1024];int size;while((size = ins.read(bytes)) > 0) bos.write(bytes,0,size);return bos.toString();}
問題:
當直接將命令字符 echo echo_test > echo.txt 傳給 java.lang.Runtime#exec()執行時報錯:
加上cmd /c 可以成功執行:
我們跟進下代碼看看是什么原因導致的?
命令執行解析流程:
傳入命令字符串echo echo_test > echo.txt進行調試,跟進java.lang.Runtime#exec(String),該方法又會調用java.lang.Runtime#exec(String,String[],File)。
在該方法中調用了StringTokenizer類,通過特定字符對命令字符串進行分割,本地測試如下:
所以命令字符串echo echo_test > echo.txt經過StringTokenizer類處理后得到命令數組:{"echo","echo_test",">","echo.txt"} 。另外java.lang.Runtime#exec()共有6個重載方法,代碼如下:
public Process exec(String command) throws IOException {return exec(command, null, null);}public Process exec(String cmdarray[]) throws IOException {return exec(cmdarray, 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.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);}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();}這6個重載函數根據參數不同進行區分,主要是傳入字符串跟數組兩種形式,但是最終調用的都是最后一個exec(String[],String[],File),在該函數內部首先調用ProcessBuilder類的構造函數創建ProcessBuilder對象,然后調用start(),最終返回一個Process對象。
所以Runtime#exec()底層還是調用的ProcessBuilder#start(),且傳入構造函數的參數要求是數組類型(如下圖),所以傳給Runtime#exec()的命令字符串需要先使用StringTokenizer類分割為數組再傳入ProcessBuilder類。
接著跟進java.lang.ProcessBuilder#start(),取出cmdarray[0]賦值給prog,如果安全管理器SecurityManager開啟,會調用SecurityManager#checkExec()對執行程序prog進行檢查,之后調用ProcessImpl#start()。
跟進java.lang.ProcessImpl#start(),Windows下會調用ProcessImpl類的構造方法,如果是Linux環境,則會調用java.lang.UNIXProcess#init<>。
跟進java.lang.ProcessImpl的構造方法
該方法內allowAmbiguousCommands變量為 "是否允許調用本地進程" 的開關,在安全管理器未開啟且jdk.lang.Process.allowAmbiguousCommands不為false時,allowAmbiguousCommands變量值才為true。當系統允許調用本地進程時,進入Legacy mode(傳統模式),會調用needsEscaping(),當prog存在空格且未被雙引號包裹時需要使用quoteString()進行處理,接著調用createCommandLine()將命令數組拼接為命令字符串,最后調用create()創建進程。
傳統模式下,當可執行程序prog存在\t 或空格時,該函數返回true,即需要雙引號包裹處理。
最后調用ProcessImpl#create(),這是一個native方法,根據JNI命名規則,會調用到ProcessImpl_md.c 中的Java_Java_lang_ProcessImpl_create(),該函數會調用Windows系統API函數:CreateProcessW(),用來創建一個新的Windows進程。創建成功后,將新進程的句柄返回給ProcessImpl#create()。
看下CreateProcessW()怎么處理我們傳入的命令的:當第一個參數(lpApplicationName)為0時,第二個參數pcmd(lpCommandLine)需要提供啟動程序及所需參數,彼此間以空格隔開。
測試 ProcessImpl#create() 方法:
加上cmd /c之后,成功執行命令:
需要添加cmd /c的原因:
在傳入 echo echo_test > echo.txt 命令字符串時,出現錯誤("java.io.IOException: Cannot run program "echo": CreateProcess error=2, 系統找不到指定的文件。")。原因是echo為命令行解釋器cmd.exe的內置命令,并不是一個單獨可執行的程序(如下圖),所以如果想執行echo命令寫文件需要先啟動cmd.exe,然后將echo命令做為cmd.exe的參數進行執行。
另外關于cmd下的 /c 參數,當未指定時,運行如下示例程序,系統會啟動一個pid為8984的cmd后臺進程,由于cmd進程未終止導致java程序卡死。當指定/c時,cmd進程會在命令執行完畢后成功終止。所以在Windows環境下,使用Runtime.getRuntime()執行的命令前綴需要加上cmd /c,使得底層Windows的processthreadsapi.h#CreateProcessW()方法在創建新進程時,可以正確識別cmd且成功返回命令執行結果。
未完待續...
360BugCloud開源漏洞響應平臺,國內自主議價漏洞收錄模式開拓者!聚焦收錄未被披露的開源以及通用組件高危漏洞,致力于維護開源軟件和供應鏈安全。平臺采用入駐邀請制,只面向成功提交未被披露漏洞的安全研究員開放。360BugCloud開源漏洞響應平臺首創“自主議價”模式及“第三方專家評審”機制,先議價后交洞,僅需提交漏洞影響力描述即可進行議價,讓安全研究員完全掌握漏洞提交主動權,高額獎金上不封頂,讓漏洞價值得到充分保障與肯定。
6步輕松實現在360BugCloud提交漏洞
360BugCloud漏洞提交地址360BugCloud開源漏洞響應平臺秉承“Trust信任、Tenet原則、Top權威、Together共建”的04T宗旨,力爭打造以技術為驅動、以安全專家為核心的應急響應平臺,提升網絡安全防護能力,為國家、企業、用戶打造最安全的網絡環境。
總結
以上是生活随笔為你收集整理的java start打开cmd窗口并停住_浅析Java命令执行的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TX2+python+pytorch i
- 下一篇: matlab 批量缩小图片