Wen Taichi

Unity Game Developer

如何從 Unity 執行批次檔 (bat) | Wen Taichi

如何從 Unity 執行批次檔 (bat)

December 28, 2024

通常我們都希望能夠看見 bat 的執行過程。如果你在網路上搜尋 “C# cmd batch keep window” 之類的關鍵字,你會找到很多人教你用 Process,並且用 cmd /k 取代 cmd /c 指令。

如果你是用 Visual Studio 開發 Console 應用程式的話,cmd /c 的功能是在你的小黑窗內使用新的 cmd 環境執行 bat,並且馬上離開該環境。如果你的 bat 有延遲效果的話,你可以從標題上觀察出環境的切換。而 cmd /k 則是在新的 cmd 環境執行 bat 完成後,不會離開該環境。但是,我們的環境是 Unity,並不是 Console 應用程式,使用 Process 呼叫 cmd 的結果就完全不一樣了。

在各種 GUI 應用程式中,使用 Process 呼叫的 cmd 指令,不論是 /c/k,都會在執行完該行後立即結束,甚至你的 batch 內有 pause 都沒用。

根據這個論壇留言和這份微軟文件,可能是直接關閉的理由,但我沒有很確定。

A console is closed when the last process attached to it terminates or calls FreeConsole.

範例批次檔

注意這個範例需要從 C# 設定環境變數,因此 UseShellExecute = true 是完全不可行的選項。

@echo off
echo Unity Version: %UNITY_VERSION%
echo Another Variable: %ANOTHER_VARIABLE%
pause

start 指令

想出來的方法是,使用第一個等等註定會被結束的 cmd 指令,執行 start 指令產生另外一個獨立的 cmd 視窗,再執行 bat。

寫 bat 相關程式碼時,一定要考慮到路徑上有空白字元的狀況,因此路徑得用雙引號 (“) 包起來。然而 start 指令若碰到雙引號,會把第一組雙引號包覆起來的字串當成視窗標題,所以只能把標題和程式路徑一起設定好。

Arguments = $"/c start \"TestProcess\" \"{batFilePath}\""

可惜結果差一點,bat 結束後,start 開啟的 cmd 視窗不會自己關閉。

Unity Version: 2021.3.18f1
Another Variable: abcde
請按任意鍵繼續 . . .

D:\Projects\Test>

cmd 搭配 exit 指令

那麼再改進一次,使用 start 搭配 cmd 來執行多重指令 (start 本身做不到),使其執行完 bat 後,額外呼叫 exit 指令把當前視窗關掉,這樣就成功了。一樣要注意雙引號。

Arguments = $"/c start \"TestProcess\" cmd /k \"\"{batFilePath}\" && exit\""

完整程式碼

using System.Diagnostics;
using System.IO;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;

public class TestCallBat
{
    [MenuItem("Tools/Call Test.bat")]
    public static void CallBat()
    {
        // 從 Unity 專案資料夾開始
        string workingDirectory = new DirectoryInfo(Application.dataPath).Parent.FullName;

        // 順便測試含有空白字元路徑的麻煩情況,注意工作路徑上不要有空白字元是個好習慣。
        string batFilePath = "Test Folder\\Test.bat";

        // 初始化 ProcessStartInfo
        var startInfo = new ProcessStartInfo
        {
            // 使用 cmd.exe 來執行 .bat
            FileName = "cmd",

            // 設定工作目錄,方便 .bat 內部使用相對路徑
            WorkingDirectory = workingDirectory,

            // 結果:網路上最容易查到的方法,但實際試過仍然會立即關閉
            //Arguments = $"/k \"{batFilePath}\"",

            // 結果:幾乎快成功了,可惜 bat 結束後,start 開啟的 cmd 視窗不會關閉
            //Arguments = $"/c start \"TestProcess\" \"{batFilePath}\"",

            // 結果:成功
            // 結論:使用 cmd /c start 來開啟標題為 TestProcess 的 cmd 視窗。
            // 然後在其中使用 cmd 指令執行 bat 檔案,並在執行完回到 TestProcess 環境後,再追加 exit 來關閉 TestProcess 視窗。
            Arguments = $"/c start \"TestProcess\" cmd /k \"\"{batFilePath}\" && exit\"",

            // 設為 false 才能設定環境變數
            UseShellExecute = false,
        };

        // 設定環境變數
        startInfo.EnvironmentVariables["UNITY_VERSION"] = Application.unityVersion;
        startInfo.EnvironmentVariables["ANOTHER_VARIABLE"] = "abcde";

        // 啟動 Process
        using Process process = Process.Start(startInfo);
        if (process != null)
        {
            // 等待執行結束
            process.WaitForExit();
            Debug.Log($"Process exited with code {process.ExitCode}");
        }
        else
        {
            Debug.LogError("Failed to start the process.");
        }
    }
}