Instrukcje: wykonywanie wiersza polecenia w języku C #, uzyskiwanie wyników STD OUT

472

Jak wykonać program wiersza polecenia z C # i odzyskać wyniki STD OUT? W szczególności chcę uruchomić DIFF na dwóch programowo wybranych plikach i zapisać wyniki w polu tekstowym.

Skrzydło
źródło
2
Zobacz także stackoverflow.com/a/5367686/492 - pokazuje zdarzenia dla danych wyjściowych i błędów.
CAD bloke
Powiązane (ale bez przechwytywania STDOUT): stackoverflow.com/questions/1469764
user202729

Odpowiedzi:

523
// Start the child process.
 Process p = new Process();
 // Redirect the output stream of the child process.
 p.StartInfo.UseShellExecute = false;
 p.StartInfo.RedirectStandardOutput = true;
 p.StartInfo.FileName = "YOURBATCHFILE.bat";
 p.Start();
 // Do not wait for the child process to exit before
 // reading to the end of its redirected stream.
 // p.WaitForExit();
 // Read the output stream first and then wait.
 string output = p.StandardOutput.ReadToEnd();
 p.WaitForExit();

Kod pochodzi z MSDN .

Ray Jezek
źródło
8
Czy można to zrobić bez pliku wsadowego? Rzecz w tym, że muszę wysłać parametry do polecenia. Korzystam z xsd.exe <Assembly> / type: <ClassName>, więc muszę mieć możliwość ustawienia zarówno zestawu, jak i nazwy klasy, a następnie uruchomić polecenie.
Carlo,
26
Możesz dodać argumenty do swojego wywołania poprzez {YourProcessObject}.StartInfo.Argumentsciąg.
patridge
5
Jak uruchomić proces jako administrator?
Saher Ahwal
5
Napotkałem szereg problemów, w których mój proces, używając tego kodu, całkowicie się zatrzymuje, ponieważ proces zapisał wystarczającą ilość danych do p.StandardErrorstrumienia. Kiedy strumień zapełni, wydaje się, że proces zostanie wstrzymany, dopóki dane są zużyte, więc muszę czytać zarówno StandardErrororaz StandardOutputw celu zagwarantowania, że to zadanie wykonuje poprawnie.
Ted Spence
5
Szybki headsup z kompilatora c #: Obiekt Process musi mieć właściwość UseShellExecute ustawioną na false, aby przekierowywać strumienie We / Wy.
IbrarMumtaz
144

Oto krótka próbka:

//Create process
System.Diagnostics.Process pProcess = new System.Diagnostics.Process();

//strCommand is path and file name of command to run
pProcess.StartInfo.FileName = strCommand;

//strCommandParameters are parameters to pass to program
pProcess.StartInfo.Arguments = strCommandParameters;

pProcess.StartInfo.UseShellExecute = false;

//Set output of program to be written to process output stream
pProcess.StartInfo.RedirectStandardOutput = true;   

//Optional
pProcess.StartInfo.WorkingDirectory = strWorkingDirectory;

//Start the process
pProcess.Start();

//Get program output
string strOutput = pProcess.StandardOutput.ReadToEnd();

//Wait for process to finish
pProcess.WaitForExit();
Jeremy
źródło
2
+1 za pokazanie, jak dodawać argumenty do działania programu wiersza polecenia (którego nie ma zaakceptowana odpowiedź)
Suman
104

Przydał mi się jeszcze jeden parametr, którego używam do wyeliminowania okna procesu

pProcess.StartInfo.CreateNoWindow = true;

pomaga to całkowicie ukryć czarne okno konsoli przed użytkownikiem, jeśli tego właśnie chcesz.

Peter Du
źródło
3
Zaoszczędził mi dużo bólu głowy. Dzięki.
Vivandiere
2
Podczas wywoływania „sc” musiałem również ustawić StartInfo.WindowStyle = ProcessWindowStyle.Hidden.
Pedro
90
// usage
const string ToolFileName = "example.exe";
string output = RunExternalExe(ToolFileName);

public string RunExternalExe(string filename, string arguments = null)
{
    var process = new Process();

    process.StartInfo.FileName = filename;
    if (!string.IsNullOrEmpty(arguments))
    {
        process.StartInfo.Arguments = arguments;
    }

    process.StartInfo.CreateNoWindow = true;
    process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    process.StartInfo.UseShellExecute = false;

    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.RedirectStandardOutput = true;
    var stdOutput = new StringBuilder();
    process.OutputDataReceived += (sender, args) => stdOutput.AppendLine(args.Data); // Use AppendLine rather than Append since args.Data is one line of output, not including the newline character.

    string stdError = null;
    try
    {
        process.Start();
        process.BeginOutputReadLine();
        stdError = process.StandardError.ReadToEnd();
        process.WaitForExit();
    }
    catch (Exception e)
    {
        throw new Exception("OS error while executing " + Format(filename, arguments)+ ": " + e.Message, e);
    }

    if (process.ExitCode == 0)
    {
        return stdOutput.ToString();
    }
    else
    {
        var message = new StringBuilder();

        if (!string.IsNullOrEmpty(stdError))
        {
            message.AppendLine(stdError);
        }

        if (stdOutput.Length != 0)
        {
            message.AppendLine("Std output:");
            message.AppendLine(stdOutput.ToString());
        }

        throw new Exception(Format(filename, arguments) + " finished with exit code = " + process.ExitCode + ": " + message);
    }
}

private string Format(string filename, string arguments)
{
    return "'" + filename + 
        ((string.IsNullOrEmpty(arguments)) ? string.Empty : " " + arguments) +
        "'";
}
Lu55
źródło
3
Bardzo wyczerpujący przykład: Dzięki
ShahidAzim
2
Może chcesz zmienić moduł obsługi OutputDataReceived na stdOut.AppendLine ()
Paul Williams
3
Moim zdaniem jest to rozwiązanie o wiele bardziej kompleksowe niż przyjęta odpowiedź. Używam go teraz i nie użyłem zaakceptowanego, ale ten naprawdę wygląda na brak.
ProfK
1
Dzięki za process.StartInfo.RedirectStandardError = true;i if (process.ExitCode == 0)która nie ma zaakceptowanej odpowiedzi.
JohnB
12

Akceptowana odpowiedź na tej stronie ma słabość, która jest kłopotliwa w rzadkich sytuacjach. Istnieją dwa uchwyty plików, do których programy piszą zgodnie z konwencją, stdout i stderr. Jeśli po prostu czytasz pojedynczy uchwyt pliku, taki jak odpowiedź Ray, a program, który uruchamiasz, zapisuje wystarczającą ilość danych wyjściowych do stderr, zapełni bufor wyjściowy i blok stderr. Wtedy twoje dwa procesy są zakleszczone. Rozmiar bufora może wynosić 4K. Jest to niezwykle rzadkie w programach krótkotrwałych, ale jeśli masz długo działający program, który wielokrotnie wysyła do stderr, w końcu się to stanie. Debugowanie i śledzenie jest trudne.

Istnieje kilka dobrych sposobów, aby sobie z tym poradzić.

  1. Jednym ze sposobów jest uruchomienie cmd.exe zamiast programu i użycie argumentu / c do cmd.exe, aby wywołać program wraz z argumentem „2> i 1” do cmd.exe, aby nakazać mu połączenie stdout i stderr.

            var p = new Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.Arguments = "/c mycmd.exe 2>&1";
  2. Innym sposobem jest użycie modelu programowania, który odczytuje oba uchwyty jednocześnie.

            var p = new Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.Arguments = @"/c dir \windows";
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardInput = false;
            p.OutputDataReceived += (a, b) => Console.WriteLine(b.Data);
            p.ErrorDataReceived += (a, b) => Console.WriteLine(b.Data);
            p.Start();
            p.BeginErrorReadLine();
            p.BeginOutputReadLine();
            p.WaitForExit();
Cameron
źródło
2
Myślę, że lepiej odpowiada to oryginalne pytanie, ponieważ pokazuje, jak uruchomić polecenie CMD przez C # (nie plik).
TinyRacoon,
12
 System.Diagnostics.ProcessStartInfo psi =
   new System.Diagnostics.ProcessStartInfo(@"program_to_call.exe");
 psi.RedirectStandardOutput = true;
 psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
 psi.UseShellExecute = false;
 System.Diagnostics.Process proc = System.Diagnostics.Process.Start(psi); ////
 System.IO.StreamReader myOutput = proc.StandardOutput;
 proc.WaitForExit(2000);
 if (proc.HasExited)
  {
      string output = myOutput.ReadToEnd();
 }
Jeff Mc
źródło
Może dojść do impasu, gdy proces zapisuje dużo danych. Lepiej jest zacząć odczytywać dane, gdy proces nadal trwa.
JensG
6

Będziesz musiał używać ProcessStartInfoz RedirectStandardOutputwłączoną - wtedy możesz odczytać strumień wyjściowy. Możesz łatwiej użyć „>”, aby przekierować dane wyjściowe do pliku (przez system operacyjny), a następnie po prostu odczytać plik.

[edytuj: jak to zrobił Ray: +1]

Marc Gravell
źródło
10
To zmusza cię do napisania pliku w miejscu, do którego potrzebujesz pozwolenia, musisz znaleźć lokalizację i nazwę i nie możesz zapomnieć o usunięciu, gdy skończysz. Łatwiejszy w użyciu RedirectStandardOutput.
peSHIr
4

Jeśli nie masz nic przeciwko wprowadzeniu zależności, CliWrap może uprościć to dla Ciebie:

var cli = new Cli("target.exe");
var output = await cli.ExecuteAsync("arguments", "stdin");
var stdout = output.StandardOutput;
Tyrrrz
źródło
3

To może nie być najlepszy / najłatwiejszy sposób, ale może być opcją:

Po uruchomieniu z kodu dodaj „> output.txt”, a następnie wczytaj plik output.txt.

Kon
źródło
3

Możesz uruchomić dowolny program wiersza poleceń za pomocą klasy Process i ustawić właściwość StandardOutput instancji Process za pomocą utworzonego czytnika strumieniowego (na podstawie ciągu znaków lub lokalizacji pamięci). Po zakończeniu procesu możesz wykonać dowolną różnicę w tym strumieniu.

Nacięcie
źródło
3

Może to być przydatne dla kogoś, kto próbuje wysłać zapytanie do lokalnej pamięci podręcznej ARP na komputerze / serwerze.

List<string[]> results = new List<string[]>();

        using (Process p = new Process())
        {
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.Arguments = "/c arp -a";
            p.StartInfo.FileName = @"C:\Windows\System32\cmd.exe";
            p.Start();

            string line;

            while ((line = p.StandardOutput.ReadLine()) != null)
            {
                if (line != "" && !line.Contains("Interface") && !line.Contains("Physical Address"))
                {
                    var lineArr = line.Trim().Split(' ').Select(n => n).Where(n => !string.IsNullOrEmpty(n)).ToArray();
                    var arrResult = new string[]
                {
                   lineArr[0],
                   lineArr[1],
                   lineArr[2]
                };
                    results.Add(arrResult);
                }
            }

            p.WaitForExit();
        }
Kitson88
źródło
3

Polecenie uruchamiania w jednym wierszu:

new Process() { StartInfo = new ProcessStartInfo("echo", "Hello, World") }.Start();

Odczytaj wynik polecenia w najkrótszej ilości kodu reable:

    var cliProcess = new Process() {
        StartInfo = new ProcessStartInfo("echo", "Hello, World") {
            UseShellExecute = false,
            RedirectStandardOutput = true
        }
    };
    cliProcess.Start();
    string cliOut = cliProcess.StandardOutput.ReadToEnd();
    cliProcess.WaitForExit();
    cliProcess.Close();
Dan Stevens
źródło
2

W otwartym kodzie publicznym PublicDomain znajduje się klasa ProcessHelper, która może Cię zainteresować.

Shaik
źródło
2

Jeśli musisz także wykonać jakieś polecenie w cmd.exe, możesz wykonać następujące czynności:

// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/C vol";
p.Start();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Console.WriteLine(output);

Zwraca tylko wynik samego polecenia:

wprowadź opis zdjęcia tutaj

Możesz również użyć StandardInputzamiast StartInfo.Arguments:

// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "cmd.exe";
p.Start();
// Read the output stream first and then wait.
p.StandardInput.WriteLine("vol");
p.StandardInput.WriteLine("exit");
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Console.WriteLine(output);

Wynik wygląda następująco:

wprowadź opis zdjęcia tutaj

Konard
źródło
0

Dla zabawy, oto moje kompletne rozwiązanie umożliwiające uzyskanie danych wyjściowych PYTHON - pod jednym kliknięciem przycisku - z raportowaniem błędów. Wystarczy dodać przycisk o nazwie „butPython” i etykietę „llHello” ...

    private void butPython(object sender, EventArgs e)
    {
        llHello.Text = "Calling Python...";
        this.Refresh();
        Tuple<String,String> python = GoPython(@"C:\Users\BLAH\Desktop\Code\Python\BLAH.py");
        llHello.Text = python.Item1; // Show result.
        if (python.Item2.Length > 0) MessageBox.Show("Sorry, there was an error:" + Environment.NewLine + python.Item2);
    }

    public Tuple<String,String> GoPython(string pythonFile, string moreArgs = "")
    {
        ProcessStartInfo PSI = new ProcessStartInfo();
        PSI.FileName = "py.exe";
        PSI.Arguments = string.Format("\"{0}\" {1}", pythonFile, moreArgs);
        PSI.CreateNoWindow = true;
        PSI.UseShellExecute = false;
        PSI.RedirectStandardError = true;
        PSI.RedirectStandardOutput = true;
        using (Process process = Process.Start(PSI))
            using (StreamReader reader = process.StandardOutput)
            {
                string stderr = process.StandardError.ReadToEnd(); // Error(s)!!
                string result = reader.ReadToEnd(); // What we want.
                return new Tuple<String,String> (result,stderr); 
            }
    }
Dave
źródło
0

Ponieważ większość odpowiedzi tutaj nie implementuje usingstatemant IDisposablei kilka innych rzeczy, które moim zdaniem mogą być niepotrzebne, dodam tę odpowiedź.

Dla C # 8.0

// Start a process with the filename or path with filename e.g. "cmd". Please note the 
//using statemant
using myProcess.StartInfo.FileName = "cmd";
// add the arguments - Note add "/c" if you want to carry out tge  argument in cmd and  
// terminate
myProcess.StartInfo.Arguments = "/c dir";
// Allows to raise events
myProcess.EnableRaisingEvents = true;
//hosted by the application itself to not open a black cmd window
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.CreateNoWindow = true;
// Eventhander for data
myProcess.Exited += OnOutputDataRecived;
// Eventhandler for error
myProcess.ErrorDataReceived += OnErrorDataReceived;
// Eventhandler wich fires when exited
myProcess.Exited += OnExited;
// Starts the process
myProcess.Start();
//read the output before you wait for exit
myProcess.BeginOutputReadLine();
// wait for the finish - this will block (leave this out if you dont want to wait for 
// it, so it runs without blocking)
process.WaitForExit();

// Handle the dataevent
private void OnOutputDataRecived(object sender, DataReceivedEventArgs e)
{
    //do something with your data
    Trace.WriteLine(e.Data);
}

//Handle the error
private void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
{        
    Trace.WriteLine(e.Data);
    //do something with your exception
    throw new Exception();
}    

// Handle Exited event and display process information.
private void OnExited(object sender, System.EventArgs e)
{
     Trace.WriteLine("Process exited");
}
juliański
źródło