Process.start: jak uzyskać wynik?

306

Chciałbym uruchomić zewnętrzny program wiersza poleceń z mojej aplikacji Mono / .NET. Na przykład chciałbym uruchomić mencoder . Czy to możliwe:

  1. Aby uzyskać wynik powłoki wiersza poleceń i zapisać go w polu tekstowym?
  2. Czy chcesz uzyskać wartość liczbową pokazującą pasek postępu z upływem czasu?
mocny
źródło

Odpowiedzi:

458

Po odpowiednim utworzeniu Processzestawu obiektów StartInfo:

var proc = new Process 
{
    StartInfo = new ProcessStartInfo
    {
        FileName = "program.exe",
        Arguments = "command line arguments to your executable",
        UseShellExecute = false,
        RedirectStandardOutput = true,
        CreateNoWindow = true
    }
};

następnie rozpocznij proces i przeczytaj z niego:

proc.Start();
while (!proc.StandardOutput.EndOfStream)
{
    string line = proc.StandardOutput.ReadLine();
    // do something with line
}

Możesz użyć int.Parse()lub int.TryParse()przekonwertować ciągi na wartości liczbowe. Być może najpierw będziesz musiał wykonać pewne operacje na łańcuchach, jeśli w czytanych ciągach znajdują się nieprawidłowe znaki numeryczne.

Ferruccio
źródło
4
Zastanawiałem się, jak poradzić sobie ze StandardError? BTW Naprawdę podoba mi się ten fragment kodu! ładne i czyste.
codea
3
Dzięki, ale myślę, że nie byłem pewien: czy powinienem dodać kolejną pętlę, aby to zrobić?
codea
@codea - Rozumiem. Możesz utworzyć jedną pętlę, która kończy się, gdy oba strumienie osiągną EOF. To może się trochę skomplikować, ponieważ jeden strumień nieuchronnie uderzy najpierw w EOF i nie chcesz już z niego czytać. Możesz także użyć dwóch pętli w dwóch różnych wątkach.
Ferruccio
1
czy bardziej niezawodne jest czytanie, dopóki sam proces się nie zakończy, niż czekanie na koniec strumieni?
Gusdor
@Gusdor - nie sądzę. Po zakończeniu procesu jego strumienie zostaną automatycznie zamknięte. Ponadto proces może zamknąć swoje strumienie na długo przed zakończeniem.
Ferruccio,
254

Możesz przetwarzać dane wyjściowe synchronicznie lub asynchronicznie .

1. Przykład synchroniczny

static void runCommand()
{
    Process process = new Process();
    process.StartInfo.FileName = "cmd.exe";
    process.StartInfo.Arguments = "/c DIR"; // Note the /c command (*)
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    process.Start();
    //* Read the output (or the error)
    string output = process.StandardOutput.ReadToEnd();
    Console.WriteLine(output);
    string err = process.StandardError.ReadToEnd();
    Console.WriteLine(err);
    process.WaitForExit();
}

Zauważ , że lepiej jest przetwarzać zarówno dane wyjściowe, jak i błędy : należy je obsługiwać osobno.

(*) W przypadku niektórych poleceń (tutaj StartInfo.Arguments) należy dodać /c dyrektywę , w przeciwnym razie proces zawiesza się w pliku WaitForExit().

2. Przykład asynchroniczny

static void runCommand() 
{
    //* Create your Process
    Process process = new Process();
    process.StartInfo.FileName = "cmd.exe";
    process.StartInfo.Arguments = "/c DIR";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    //* Set your output and error (asynchronous) handlers
    process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
    process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
    //* Start process and handlers
    process.Start();
    process.BeginOutputReadLine();
    process.BeginErrorReadLine();
    process.WaitForExit();
}

static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 
{
    //* Do your stuff with the output (write to console/log/StringBuilder)
    Console.WriteLine(outLine.Data);
}

Jeśli nie musisz wykonywać skomplikowanych operacji na danych wyjściowych, możesz ominąć metodę OutputHandler, dodając bezpośrednio procedury obsługi:

//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
T30
źródło
2
kocham asynchronię! Byłem w stanie użyć tego kodu (z niewielką transkrypcją) w VB.net
Richard Barker
note „string output = process.StandardOutput.ReadToEnd ();” może wytworzyć duży ciąg, jeśli jest wiele wierszy wyniku; przykład asynchroniczny i odpowiedź Ferruccio przetwarzają dane wyjściowe linia po linii.
Andrew Hill,
5
Uwaga: twoje pierwsze (synchroniczne) podejście jest nieprawidłowe! NIE powinieneś czytać jednocześnie StandardOutput i StandardError! spowoduje martwe blokady. przynajmniej jeden z nich musi być asynchroniczny.
S.Serpooshan
6
Process.WaitForExit () blokuje wątki, dlatego jest synchroniczny. Nie o to chodzi w odpowiedzi, ale pomyślałem, że mogę to dodać. Dodaj proces.EnableRaisingEvents = true i skorzystaj ze zdarzenia Exited, aby być w pełni asynchronicznym.
Tom
Czy nie można bezpośrednio przekierować? Używam całego kolorowania wyjścia sass?
Ini
14

W porządku, dla każdego, kto chce odczytać zarówno Błędy, jak i Wyjścia, ale dostaje impasu w przypadku dowolnego z rozwiązań przedstawionych w innych odpowiedziach (takich jak ja), oto rozwiązanie, które zbudowałem po przeczytaniu wyjaśnienia MSDN dotyczącego StandardOutputwłaściwości.

Odpowiedź jest oparta na kodzie T30:

static void runCommand()
{
    //* Create your Process
    Process process = new Process();
    process.StartInfo.FileName = "cmd.exe";
    process.StartInfo.Arguments = "/c DIR";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    //* Set ONLY ONE handler here.
    process.ErrorDataReceived += new DataReceivedEventHandler(ErrorOutputHandler);
    //* Start process
    process.Start();
    //* Read one element asynchronously
    process.BeginErrorReadLine();
    //* Read the other one synchronously
    string output = process.StandardOutput.ReadToEnd();
    Console.WriteLine(output);
    process.WaitForExit();
}

static void ErrorOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 
{
    //* Do your stuff with the output (write to console/log/StringBuilder)
    Console.WriteLine(outLine.Data);
}
Cubrman
źródło
Dzięki za dodanie tego. Czy mogę zapytać, jakiego polecenia użyłeś?
T30
Tworzę aplikację w języku c #, która ma na celu uruchomienie mysqldump.exe, pokazywanie użytkownikowi każdej wiadomości generowanej przez aplikację, czekanie na jej zakończenie, a następnie wykonywanie kilku innych zadań. Nie rozumiem, o jakim poleceniu mówisz? Całe to pytanie dotyczy uruchomienia procesu od c #.
cubrman
1
jeśli użyjesz dwóch osobnych programów obsługi, nie dostaniesz impasu
Ovi
również w twoim przykładzie czytasz ten proces. Standardowy Wyjście tylko raz ... zaraz po jego uruchomieniu, ale ktoś chciałby czytać go w sposób ciągły, gdy proces jest uruchomiony, nie?
Ovi
7

Standardowym sposobem realizacji tego .NET jest czytanie ze strumienia StandardOutput procesu . Istnieje przykład w połączonych dokumentach MSDN. Podobnie możesz czytać ze StandardError i pisać do StandardInput .

driis
źródło
4

możesz użyć pamięci współdzielonej dla 2 procesów do komunikacji, sprawdź MemoryMappedFile

przede wszystkim utworzysz plik odwzorowany mmfw pamięci w procesie nadrzędnym za pomocą instrukcji „using”, a następnie utworzysz drugi proces, aż zostanie zakończony, i pozwolisz mu zapisać wynik za mmfpomocą BinaryWriter, a następnie odczytać wynik za mmfpomocą procesu nadrzędnego, możesz także przekazać mmfnazwę za pomocą argumentów wiersza poleceń lub wpisać go na stałe.

upewnij się, że w przypadku korzystania z pliku odwzorowanego w procesie nadrzędnym proces potomny zapisuje wynik do pliku odwzorowanego przed zwolnieniem pliku odwzorowanego w procesie nadrzędnym

Przykład: proces nadrzędny

    private static void Main(string[] args)
    {
        using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("memfile", 128))
        {
            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryWriter writer = new BinaryWriter(stream);
                writer.Write(512);
            }

            Console.WriteLine("Starting the child process");
            // Command line args are separated by a space
            Process p = Process.Start("ChildProcess.exe", "memfile");

            Console.WriteLine("Waiting child to die");

            p.WaitForExit();
            Console.WriteLine("Child died");

            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryReader reader = new BinaryReader(stream);
                Console.WriteLine("Result:" + reader.ReadInt32());
            }
        }
        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

Proces potomny

    private static void Main(string[] args)
    {
        Console.WriteLine("Child process started");
        string mmfName = args[0];

        using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(mmfName))
        {
            int readValue;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryReader reader = new BinaryReader(stream);
                Console.WriteLine("child reading: " + (readValue = reader.ReadInt32()));
            }
            using (MemoryMappedViewStream input = mmf.CreateViewStream())
            {
                BinaryWriter writer = new BinaryWriter(input);
                writer.Write(readValue * 2);
            }
        }

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

aby użyć tego przykładu, musisz utworzyć rozwiązanie z 2 projektami w środku, a następnie pobrać wynik kompilacji procesu potomnego z% childDir% / bin / debug i skopiować go do% parentDirectory% / bin / debug, a następnie uruchomić projekt nadrzędny

childDiri parentDirectoryto nazwy folderów twoich projektów na PC powodzenia :)

shakram02
źródło
1

Jak uruchomić proces (np. Plik nietoperza, skrypt perla, program konsoli) i wyświetlać standardowe wyjście w formularzu systemu Windows:

processCaller = new ProcessCaller(this);
//processCaller.FileName = @"..\..\hello.bat";
processCaller.FileName = @"commandline.exe";
processCaller.Arguments = "";
processCaller.StdErrReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.StdOutReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.Completed += new EventHandler(processCompletedOrCanceled);
processCaller.Cancelled += new EventHandler(processCompletedOrCanceled);
// processCaller.Failed += no event handler for this one, yet.

this.richTextBox1.Text = "Started function.  Please stand by.." + Environment.NewLine;

// the following function starts a process and returns immediately,
// thus allowing the form to stay responsive.
processCaller.Start();    

Można znaleźć ProcessCallerpod tym linkiem: Uruchomienie procesu i wyświetlenie jego standardowego wyjścia

abberdeen
źródło
1

Możesz rejestrować dane wyjściowe procesu za pomocą poniższego kodu:

ProcessStartInfo pinfo = new ProcessStartInfo(item);
pinfo.CreateNoWindow = false;
pinfo.UseShellExecute = true;
pinfo.RedirectStandardOutput = true;
pinfo.RedirectStandardInput = true;
pinfo.RedirectStandardError = true;
pinfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
var p = Process.Start(pinfo);
p.WaitForExit();
Process process = Process.Start(new ProcessStartInfo((item + '>' + item + ".txt"))
{
    UseShellExecute = false,
    RedirectStandardOutput = true
});
process.WaitForExit();
string output = process.StandardOutput.ReadToEnd();
if (process.ExitCode != 0) { 
}
SHUBHAM PATIDAR
źródło
1

Rozwiązaniem, które zadziałało dla mnie w systemie Windows i Linux, jest postęp

// GET api/values
        [HttpGet("cifrado/{xml}")]
        public ActionResult<IEnumerable<string>> Cifrado(String xml)
        {
            String nombreXML = DateTime.Now.ToString("ddMMyyyyhhmmss").ToString();
            String archivo = "/app/files/"+nombreXML + ".XML";
            String comando = " --armor --recipient [email protected]  --encrypt " + archivo;
            try{
                System.IO.File.WriteAllText(archivo, xml);                
                //String comando = "C:\\GnuPG\\bin\\gpg.exe --recipient [email protected] --armor --encrypt C:\\Users\\Administrador\\Documents\\pruebas\\nuevo.xml ";
                ProcessStartInfo startInfo = new ProcessStartInfo() {FileName = "/usr/bin/gpg",  Arguments = comando }; 
                Process proc = new Process() { StartInfo = startInfo, };
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.RedirectStandardError = true;
                proc.Start();
                proc.WaitForExit();
                Console.WriteLine(proc.StandardOutput.ReadToEnd());
                return new string[] { "Archivo encriptado", archivo + " - "+ comando};
            }catch (Exception exception){
                return new string[] { archivo, "exception: "+exception.ToString() + " - "+ comando };
            }
        }
Jorge Santos Neill
źródło