Przechwytywanie danych wyjściowych konsoli z aplikacji .NET (C #)

133

Jak wywołać aplikację konsoli z mojej aplikacji .NET i przechwycić wszystkie dane wyjściowe wygenerowane w konsoli?

(Pamiętaj, nie chcę najpierw zapisywać informacji w pliku, a następnie wystawiać ponownie, tak jak chciałbym otrzymać je na żywo).

Gripsoft
źródło
Możliwy duplikat Process.start: jak uzyskać wynik?
Michael Freidgeim
Proszę zapoznać się z datami w obu pytaniach i sprawdzić, które z nich jest „duplikatem”
Gripsoft
„Możliwy duplikat” to sposób na uporządkowanie - zamknięcie podobnych pytań i pozostawienie jednego z najlepszymi odpowiedziami. Data nie jest konieczna. Zobacz Czy powinienem zagłosować za zamknięciem zduplikowanego pytania, mimo że jest dużo nowsze i zawiera bardziej aktualne odpowiedzi? Jeśli zgadzasz się, że wymaga to wyjaśnienia, zagłosuj na Dodaj link wyjaśniający do automatycznego komentarza „Możliwy duplikat”
Michael Freidgeim

Odpowiedzi:

167

Można to dość łatwo osiągnąć za pomocą właściwości ProcessStartInfo.RedirectStandardOutput . Pełny przykład znajduje się w połączonej dokumentacji MSDN; jedynym zastrzeżeniem jest to, że może być konieczne przekierowanie standardowego strumienia błędów, aby zobaczyć wszystkie dane wyjściowe aplikacji.

Process compiler = new Process();
compiler.StartInfo.FileName = "csc.exe";
compiler.StartInfo.Arguments = "/r:System.dll /out:sample.exe stdstr.cs";
compiler.StartInfo.UseShellExecute = false;
compiler.StartInfo.RedirectStandardOutput = true;
compiler.Start();    

Console.WriteLine(compiler.StandardOutput.ReadToEnd());

compiler.WaitForExit();
mdb
źródło
3
Jeśli nie chcesz dodatkowej nowej linii na końcu, po prostu użyj Console.Writezamiast tego.
tm1
3
Należy zauważyć, że jeśli używasz ReadToEnd () w połączeniu z aplikacją konsolową, która ma możliwość monitowania użytkownika o wprowadzenie danych. Np .: Zastąp plik: T czy N? etc Następnie ReadToEnd może spowodować wyciek pamięci, ponieważ proces nigdy nie kończy się podczas oczekiwania na dane wejściowe użytkownika. Bezpieczniejszym sposobem przechwytywania danych wyjściowych jest użycie procedury obsługi zdarzeń process.OutputDataReceived i umożliwienie procesowi powiadamiania aplikacji o otrzymanych wynikach.
Baaleos
Jak przechwytywać, jeśli kod jest wdrażany w aplikacji Azure Webapp, ponieważ compiler.StartInfo.FileName = "csc.exe"; może nie istnieć!
Asif Iqbal
Jak przechwytywać, jeśli kod jest wdrażany w aplikacji Azure Webapp, ponieważ compiler.StartInfo.FileName = "csc.exe"; może nie istnieć!
Asif Iqbal
42

Jest to nieco lepsze od akceptowanej odpowiedzi z @mdb . W szczególności przechwytujemy również wyjście błędów procesu. Ponadto przechwytujemy te dane wyjściowe za pośrednictwem zdarzeń, ponieważ ReadToEnd()nie działa, jeśli chcesz przechwytywać zarówno błędy, jak i zwykłe dane wyjściowe. Zajęło mi trochę czasu, aby to zadziałało, ponieważ w rzeczywistości wymaga to również BeginxxxReadLine()połączeń po Start().

Sposób asynchroniczny:

using System.Diagnostics;

Process process = new Process();

void LaunchProcess()
{
    process.EnableRaisingEvents = true;
    process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_OutputDataReceived);
    process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_ErrorDataReceived);
    process.Exited += new System.EventHandler(process_Exited);

    process.StartInfo.FileName = "some.exe";
    process.StartInfo.Arguments = "param1 param2";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.RedirectStandardOutput = true;

    process.Start();
    process.BeginErrorReadLine();
    process.BeginOutputReadLine();          

    //below line is optional if we want a blocking call
    //process.WaitForExit();
}

void process_Exited(object sender, EventArgs e)
{
    Console.WriteLine(string.Format("process exited with code {0}\n", process.ExitCode.ToString()));
}

void process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.WriteLine(e.Data + "\n");
}

void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.WriteLine(e.Data + "\n");
}
Shital Shah
źródło
5
Dziękuję, szukałem tego od wieków!
C Bauer,
3
Dziękuję Ci. To jest doskonałe.
DrFloyd5
1
Otrzymasz honorowe miejsce na liście podziękowań mojej aplikacji.
marsh-wiggle
to jest łatwy do zrozumienia, piękny kod. Moim jedynym chwytakiem jest to, że niepotrzebnie dodajesz nowe linie. "Writeline" dodaje jeden za ciebie, więc efektem netto jest to, że przechwycone dane wyjściowe mają pustą linię wstawioną co drugą linię.
John Lord
7

ConsoleAppLauncher to biblioteka open source stworzona specjalnie po to, aby odpowiedzieć na to pytanie. Przechwytuje wszystkie dane wyjściowe generowane w konsoli i zapewnia prosty interfejs do uruchamiania i zamykania aplikacji konsoli.

Zdarzenie ConsoleOutput jest wywoływane za każdym razem, gdy konsola zapisuje nowy wiersz na wyjście standardowe / błąd. Wiersze są umieszczane w kolejce i na pewno będą zgodne z kolejnością wyjściową.

Dostępne również jako pakiet NuGet .

Przykładowe wywołanie, aby uzyskać pełne dane wyjściowe konsoli:

// Run simplest shell command and return its output.
public static string GetWindowsVersion()
{
    return ConsoleApp.Run("cmd", "/c ver").Output.Trim();
}

Próbka z opiniami na żywo:

// Run ping.exe asynchronously and return roundtrip times back to the caller in a callback
public static void PingUrl(string url, Action<string> replyHandler)
{
    var regex = new Regex("(time=|Average = )(?<time>.*?ms)", RegexOptions.Compiled);
    var app = new ConsoleApp("ping", url);
    app.ConsoleOutput += (o, args) =>
    {
        var match = regex.Match(args.Line);
        if (match.Success)
        {
            var roundtripTime = match.Groups["time"].Value;
            replyHandler(roundtripTime);
        }
    };
    app.Run();
}
SlavaGu
źródło
2

Dodałem kilka metod pomocniczych do Platformy O2 (projekt Open Source), które pozwalają łatwo skryptować interakcję z innym procesem poprzez wyjście i wejście konsoli (patrz http://code.google.com/p/o2platform/ źródło / przeglądanie / trunk / O2_Scripts / APIs / Windows / CmdExe / CmdExeAPI.cs )

Przydatne może być również API, które umożliwia przeglądanie wyników konsolowych bieżącego procesu (w istniejącej kontrolce lub oknie podręcznym). Zobacz ten post na blogu, aby uzyskać więcej informacji: http://o2platform.wordpress.com/2011/11/26/api_consoleout-cs-inprocess-capture-of-the-console-output/ (ten blog zawiera również szczegółowe informacje o tym, jak konsumować konsolowe dane wyjściowe nowych procesów)

Dinis Cruz
źródło
Od tego czasu dodałem więcej wsparcia dla korzystania z ConsoleOut (w tym przypadku, jeśli sam uruchamiasz proces .NET). Spójrz na: Jak używać danych wyjściowych konsoli w C # REPL , dodawanie „wyjścia konsoli” do środowiska VisualStudio IDE jako natywnego okna , wyświetlanie komunikatów „wyjście konsoli” utworzonych w UserControls
Dinis Cruz
2

Zrobiłem wersję reaktywną, która akceptuje wywołania zwrotne dla stdOut i StdErr.
onStdOuti onStdErrsą wywoływane asynchronicznie,
gdy tylko nadejdą dane (przed zakończeniem procesu).

public static Int32 RunProcess(String path,
                               String args,
                       Action<String> onStdOut = null,
                       Action<String> onStdErr = null)
    {
        var readStdOut = onStdOut != null;
        var readStdErr = onStdErr != null;

        var process = new Process
        {
            StartInfo =
            {
                FileName = path,
                Arguments = args,
                CreateNoWindow = true,
                UseShellExecute = false,
                RedirectStandardOutput = readStdOut,
                RedirectStandardError = readStdErr,
            }
        };

        process.Start();

        if (readStdOut) Task.Run(() => ReadStream(process.StandardOutput, onStdOut));
        if (readStdErr) Task.Run(() => ReadStream(process.StandardError, onStdErr));

        process.WaitForExit();

        return process.ExitCode;
    }

    private static void ReadStream(TextReader textReader, Action<String> callback)
    {
        while (true)
        {
            var line = textReader.ReadLine();
            if (line == null)
                break;

            callback(line);
        }
    }


Przykładowe użycie

Następujące będzie działać executablez argsi drukować

  • stdOut w kolorze białym
  • stdErr na czerwono

do konsoli.

RunProcess(
    executable,
    args,
    s => { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(s); },
    s => { Console.ForegroundColor = ConsoleColor.Red;   Console.WriteLine(s); } 
);
3dGrabber
źródło
1

Z PythonTR - Python Programcıları Derneği, e-kitap, örnek :

Process p = new Process();   // Create new object
p.StartInfo.UseShellExecute = false;  // Do not use shell
p.StartInfo.RedirectStandardOutput = true;   // Redirect output
p.StartInfo.FileName = "c:\\python26\\python.exe";   // Path of our Python compiler
p.StartInfo.Arguments = "c:\\python26\\Hello_C_Python.py";   // Path of the .py to be executed
livetogogo
źródło
1

Dodano process.StartInfo.**CreateNoWindow** = true;i timeout.

private static void CaptureConsoleAppOutput(string exeName, string arguments, int timeoutMilliseconds, out int exitCode, out string output)
{
    using (Process process = new Process())
    {
        process.StartInfo.FileName = exeName;
        process.StartInfo.Arguments = arguments;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.CreateNoWindow = true;
        process.Start();

        output = process.StandardOutput.ReadToEnd();

        bool exited = process.WaitForExit(timeoutMilliseconds);
        if (exited)
        {
            exitCode = process.ExitCode;
        }
        else
        {
            exitCode = -1;
        }
    }
}
Siergiej Zinowjew
źródło
Kiedy StandardOutput.ReadToEnd()go użyjesz , nie wróci do następnej instrukcji aż do końca aplikacji. więc Twój limit czasu w WaitForExit (timeoutMilliseconds) nie działa! (Twój kod się zawiesi!)
S.Serpooshan