Process.start: como obter a saída?

306

Gostaria de executar um programa de linha de comando externo no meu aplicativo Mono / .NET. Por exemplo, eu gostaria de executar o mencoder . É possível:

  1. Para obter a saída do shell da linha de comando e escrevê-la na minha caixa de texto?
  2. Para obter o valor numérico para mostrar uma barra de progresso com o tempo decorrido?
firme
fonte

Respostas:

458

Quando você cria seu Processconjunto de objetos StartInfoadequadamente:

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

em seguida, inicie o processo e leia a partir dele:

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

Você pode usar int.Parse()ou int.TryParse()converter as seqüências de caracteres em valores numéricos. Pode ser necessário primeiro manipular as strings se houver caracteres numéricos inválidos nas strings que você lê.

Ferruccio
fonte
4
Eu queria saber como você poderia lidar com o StandardError? BTW, eu realmente gosto deste trecho de código! Legal e Limpo.
codea
3
Obrigado, mas acho que não estava claro: devo adicionar outro loop para fazer isso?
codea
@ codea - entendo. Você pode criar um loop que termina quando os dois fluxos atingem o EOF. Isso pode ficar um pouco complicado, porque um fluxo inevitavelmente atingirá o EOF primeiro e você não deseja mais lê-lo. Você também pode usar dois loops em dois threads diferentes.
Ferruccio
1
é mais robusto ler até o processo terminar, em vez de esperar pelo fim dos fluxos?
Gusdor #
@ Gusdor - acho que não. Quando o processo terminar, seus fluxos serão fechados automaticamente. Além disso, um processo pode fechar seus fluxos muito antes de terminar.
Ferruccio
254

Você pode processar sua saída de forma síncrona ou assíncrona .

1. Exemplo síncrono

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();
}

Observe que é melhor processar a saída e os erros : eles devem ser tratados separadamente.

(*) Para alguns comandos (aqui StartInfo.Arguments), você deve adicionar a /c diretiva , caso contrário, o processo congela no WaitForExit().

2. Exemplo assíncrono

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);
}

Se você não precisar realizar operações complicadas com a saída, poderá ignorar o método OutputHandler, apenas adicionando os manipuladores diretamente na linha:

//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
T30
fonte
2
tem que amar assíncrona! Eu era capaz de usar esse código (com um pouco de transcrição) no VB.net
Richard Barker
note 'string output = process.StandardOutput.ReadToEnd ();' pode produzir uma cadeia grande se houver muitas linhas de saída; o exemplo assíncrono e a resposta de Ferruccio processam a saída linha por linha.
Andrew Hill
5
Nota: sua primeira abordagem (síncrona) não está correta! Você NÃO deve ler StandardOutput e StandardError de forma síncrona! Isso causará bloqueios. pelo menos um deles deve ser assíncrono.
precisa saber é o seguinte
6
Process.WaitForExit () é um bloqueio de thread, portanto síncrono. Não é o ponto da resposta, mas achei que poderia acrescentar isso. Inclua process.EnableRaisingEvents = true e faça uso do evento Exited para ser totalmente assíncrono.
Tom
Não é possível redirecionar diretamente? Eu uso toda a colorização da saída sass?
Ini
14

Tudo bem, para quem deseja ler erros e saídas, mas obtém impasses com qualquer uma das soluções, fornecidas em outras respostas (como eu), aqui está uma solução que eu criei após ler a explicação do MSDN para a StandardOutputpropriedade.

A resposta é baseada no código do 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
fonte
Obrigado por adicionar isso. Posso perguntar qual comando você estava usando?
T30 14/02
Estou desenvolvendo um aplicativo em c # projetado para iniciar um mysqldump.exe, mostrar ao usuário todas as mensagens que o aplicativo gera, aguardar o término e executar mais algumas tarefas. Não consigo entender de que tipo de comando você está falando? Toda essa pergunta é sobre o lançamento de um processo a partir do c #.
cubrman
1
se você usar dois manipuladores separados você não vai conseguir impasses
Ovi
Também no exemplo, você lê o processo. Saída padrão apenas uma vez ... logo após iniciá-lo, mas você gostaria de lê-lo continuamente enquanto o processo está em execução, não?
Ovi
7

A maneira padrão do .NET de fazer isso é ler o fluxo StandardOutput do processo . Há um exemplo nos documentos vinculados do MSDN. Similar, você pode ler do StandardError e gravar no StandardInput .

driis
fonte
4

você pode usar memória compartilhada para os 2 processos para se comunicar, confira MemoryMappedFile

você criará principalmente um arquivo mapeado de memória mmfno processo pai usando a instrução "using" e depois criará o segundo processo até que termine e deixe gravar o resultado no mmfuso BinaryWriter, depois ler o resultado mmfno processo pai usando, você também pode passe o mmfnome usando argumentos de linha de comando ou codifique-o.

ao usar o arquivo mapeado no processo pai, certifique-se de que o processo filho grave o resultado no arquivo mapeado antes que o arquivo mapeado seja liberado no processo pai

Exemplo: processo pai

    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();
    }

Processo filho

    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();
    }

Para usar esse exemplo, você precisará criar uma solução com 2 projetos internos, pegar o resultado da construção do processo filho de% childDir% / bin / debug e copiá-lo para% parentDirectory% / bin / debug e executar o projeto pai

childDire parentDirectorysão os nomes das pastas dos seus projetos no pc boa sorte :)

shakram02
fonte
1

Como iniciar um processo (como um arquivo bat, script perl, programa do console) e ter sua saída padrão exibida no formulário do 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();    

Você pode encontrar ProcessCallerneste link: Iniciando um processo e exibindo sua saída padrão

Aberdeen
fonte
1

Você pode registrar a saída do processo usando o código abaixo:

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
fonte
1

A solução que funcionou para mim no win e no linux é a seguinte

// 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
fonte