Как создать процесс и записать его STDOUT в .NET?

Мне нужно создать дочерний процесс, который является консольным приложением, и записать его вывод.

Я написал следующий код для метода:

string retMessage = String.Empty;
ProcessStartInfo startInfo = new ProcessStartInfo();
Process p = new Process();

startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;

startInfo.UseShellExecute = false;
startInfo.Arguments = command;
startInfo.FileName = exec;

p.StartInfo = startInfo;
p.Start();

p.OutputDataReceived += new DataReceivedEventHandler
(
    delegate(object sender, DataReceivedEventArgs e)
    {
        using (StreamReader output = p.StandardOutput)
        {
            retMessage = output.ReadToEnd();
        }
    }
);

p.WaitForExit();

return retMessage;

Однако это ничего не возвращает. Я не верю, что событие OutputDataReceived вызывается, или команда WaitForExit() может блокировать поток, поэтому он никогда не будет выполнять обратный вызов.

Любой совет?

Обновлено: Похоже, я слишком старался с обратным вызовом. Делает:

return p.StandardOutput.ReadToEnd(); 

Кажется, работает нормально.

Если вы не собираетесь взаимодействовать с приложением и заботитесь только о его выводе, вам не следует использовать для этого способ Async BeginOutputReadLine() и Start(). Я обнаружил, что они не очень надежны и иногда могут обрезать начало вывода приложения.

Michael Graczyk 17.07.2012 03:47

Почему не retMessage = e.Data? Это уже строковая переменная :)

Nickon 02.09.2013 12:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
155
2
123 345
9

Ответы 9

Вам нужно вызвать p.Start (), чтобы фактически запустить процесс после того, как вы установили StartInfo. Как бы то ни было, ваша функция, вероятно, зависает при вызове WaitForExit (), потому что процесс на самом деле никогда не запускался.

это была опечатка, так как я удалял код, чтобы сделать образец, я случайно вырезал эту строку.

FlySwat 13.11.2008 02:24

Вот код, работоспособность которого я проверил. Я использую его для создания MSBuild и прослушивания его вывода:

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (sender, args) => Console.WriteLine("received output: {0}", args.Data);
process.Start();
process.BeginOutputReadLine();

Важнейшей командой, которая исправляет OP, является добавление BeginOutputReadLine ()

Mark Lakata 16.05.2012 02:16

Большое спасибо, @Judah Himango

Gezim 25.08.2012 04:20

Я не могу поверить, сколько людей упускают из виду часть «BeginOutputReadLine». Спас мои дни, спасибо!

Eugen Timm 17.07.2014 17:01

Чтобы также фиксировать ошибки, добавьте RedirectStandardError = true, process.ErrorDataReceived += (sender, args) => Console.WriteLine(args.Data); и process.BeginErrorReadLine();.

magnusarinell 29.02.2016 11:31

Любопытно, есть ли время между Start () и BeginOutputReadLine (), когда вывод может быть потерян (не передается в OutputDataReceived)?

Vimes 11.05.2017 19:40

Похоже, две ваши строки вышли из строя. Вы запускаете процесс перед настройкой обработчика событий для захвата вывода. Возможно, процесс только что завершается до того, как будет добавлен обработчик событий.

Переключите строки вот так.

p.OutputDataReceived += ...
p.Start();        

Несмотря на то, что он не отмечен как таковой, это, вероятно, правильный ответ.

Casper Leon Nielsen 25.01.2012 18:27

У меня это не сработало. РЕДАКТИРОВАНИЕ, которое FlySwat включил в свой ответ, сработало для меня.

Mark Lakata 16.05.2012 02:15

@CasperLeonNielsen, то же самое.

as9876 16.04.2015 06:24

Это не правильно. Чтобы это сработало, вам нужно сначала вызвать BeginOutputReadLine, который начинает читать поток stdOut. Вы также можете читать поток напрямую. К сожалению, выполнение создает потоки ввода-вывода только после того, как процесс началось. Вызов BeginOutputReadLine или чтение из потока перед вызовом process.Start() вызовет выброс, потому что поток еще не существует.

3dGrabber 17.12.2019 13:16

Я просто попробовал это, и у меня сработало следующее:

StringBuilder outputBuilder;
ProcessStartInfo processStartInfo;
Process process;

outputBuilder = new StringBuilder();

processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.UseShellExecute = false;
processStartInfo.Arguments = "<insert command line arguments here>";
processStartInfo.FileName = "<insert tool path here>";

process = new Process();
process.StartInfo = processStartInfo;
// enable raising events because Process does not raise events by default
process.EnableRaisingEvents = true;
// attach the event handler for OutputDataReceived before starting the process
process.OutputDataReceived += new DataReceivedEventHandler
(
    delegate(object sender, DataReceivedEventArgs e)
    {
        // append the new data to the data already read-in
        outputBuilder.Append(e.Data);
    }
);
// start the process
// then begin asynchronously reading the output
// then wait for the process to exit
// then cancel asynchronously reading the output
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();

// use the output
string output = outputBuilder.ToString();

Я обнаружил, что это немного проще p.StartInfo = startInfo; p.Start(); output = p.StandardOutput.ReadToEnd(); p.WaitForExit();

Mike Wade 13.02.2013 20:10

@Spike, но он работает синхронно. Если вам нужно передать данные на стандартный ввод, это не сработает.

Sebastian 10.08.2014 11:26

Обратите внимание, что EnableRaisingEvents = true необходимо только для возникновения события Process.Exited.

Flatliner DOA 11.08.2016 05:26

Это прекрасно работает. Спасибо @ umar-farooq-khawaja

Matt W 10.02.2017 19:04

Должен быть "AppendLine"

zumalifeguard 20.12.2017 02:54

@zumalifeguard, если вы используете AppendLine, это добавит новые строки, где данные разбиты из-за буферизации. Добавить правильно.

Umar Farooq Khawaja 20.12.2017 19:36

Умар, понятно. Но если вы не добавите новые строки, весь текст превратится в одну большую кляксу, верно? Если вывод разделен новыми строками, то как его обработать? Что мне не хватает?

zumalifeguard 21.12.2017 21:46

Это событие запускается для обработки фрагментированных данных, поскольку размер буфера ограничен. В ваших данных уже должны быть новые строки, которые вы можете использовать для их построчной обработки.

Umar Farooq Khawaja 22.12.2017 18:10

Вот метод, который я использую для запуска процесса и получения его результатов и ошибок:

public static string ShellExecute(this string path, string command, TextWriter writer, params string[] arguments)
    {
        using (var process = Process.Start(new ProcessStartInfo { WorkingDirectory = path, FileName = command, Arguments = string.Join(" ", arguments), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true }))
        {
            using (process.StandardOutput)
            {
                writer.WriteLine(process.StandardOutput.ReadToEnd());
            }
            using (process.StandardError)
            {
                writer.WriteLine(process.StandardError.ReadToEnd());
            }
        }

        return path;
    }

Например :

@"E:\Temp\MyWorkingDirectory".ShellExecute(@"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe", Console.Out);

Разве это не тупик? Документы MSDN говорят, что вы рискуете попасть в тупик, если одновременно будете прослушивать и вывод, и ошибку. Приложение остановится, если буфер ошибок заполнится, и дождется его опустошения. Но вы не очищаете буфер ошибок до тех пор, пока буфер вывода не будет завершен (чего не будет, поскольку приложение ожидает буфер ошибок) ...

Michael Bisbjerg 31.03.2013 19:42

Вот полный и простой код для этого. Это нормально работало, когда я его использовал.

var processStartInfo = new ProcessStartInfo
{
    FileName = @"C:\SomeProgram",
    Arguments = "Arguments",
    RedirectStandardOutput = true,
    UseShellExecute = false
};
var process = Process.Start(processStartInfo);
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

Обратите внимание, что это захватывает только стандартный выход; он не захватывает стандартный ошибка. Если вы хотите и то, и другое, используйте эта техника для каждого потока.

Это самый простой ответ

jjxtra 28.01.2021 19:55

Перенаправление потока является асинхронным и потенциально будет продолжаться после завершения процесса. Умар упоминает об отмене после завершения процесса process.CancelOutputRead(). Однако это может привести к потере данных.

Это надежно работает для меня:

process.WaitForExit(...);
...
while (process.StandardOutput.EndOfStream == false)
{
    Thread.Sleep(100);
}

Я не пробовал этот подход, но мне нравится предложение Слая:

if (process.WaitForExit(timeout))
{
    process.WaitForExit();
}

При чтении потоков вывода / ошибок асинхронно вызывать process.WaitForExit () (версия без параметров) после того, как process.WaitForExit (тайм-аут) вернул true. Это будет блокироваться до тех пор, пока не будет достигнут конец потоков вывода / ошибок. Подробнее см. Примечания здесь: msdn.microsoft.com/en-us/library/ty0d8k56%28v=vs.110%29

Sly 10.09.2014 22:17

Мне нужно было захватить как stdout, так и stderr и иметь тайм-аут, если процесс не завершился, как ожидалось. Я придумал это:

Process process = new Process();
StringBuilder outputStringBuilder = new StringBuilder();

try
{
process.StartInfo.FileName = exeFileName;
process.StartInfo.WorkingDirectory = args.ExeDirectory;
process.StartInfo.Arguments = args;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = false;
process.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.ErrorDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var processExited = process.WaitForExit(PROCESS_TIMEOUT);

if (processExited == false) // we timed out...
{
    process.Kill();
    throw new Exception("ERROR: Process took too long to finish");
}
else if (process.ExitCode != 0)
{
    var output = outputStringBuilder.ToString();
    var prefixMessage = "";

    throw new Exception("Process exited with non-zero exit code of: " + process.ExitCode + Environment.NewLine + 
    "Output from process: " + outputStringBuilder.ToString());
}
}
finally
{                
process.Close();
}

Я объединяю stdout и stderr в одну и ту же строку, но при необходимости вы можете сохранить их отдельно. Он использует события, поэтому должен обрабатывать их по мере их поступления (я считаю). Я успешно выполнил это и скоро буду тестировать его.

Это сработало для меня! Благодарность

Raha 29.08.2016 00:50

Ответ от Иуды у меня не сработал (или не полный), так как приложение выходило после первого BeginOutputReadLine();

Это работает для меня как полный фрагмент, читающий постоянный вывод пинга:

        var process = new Process();
        process.StartInfo.FileName = "ping";
        process.StartInfo.Arguments = "google.com -t";
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.UseShellExecute = false;
        process.OutputDataReceived += (sender, a) => Console.WriteLine(a.Data);
        process.Start();
        process.BeginOutputReadLine();
        process.WaitForExit();

Другие вопросы по теме