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?

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ê.


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

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

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



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 :)


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


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

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 bibankingprd@bi.com.gt  --encrypt " + archivo;
            try{
                System.IO.File.WriteAllText(archivo, xml);                
                //String comando = "C:\\GnuPG\\bin\\gpg.exe --recipient licorera@local.com --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 };
            }
        }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.