How to read from Process.StandardOutput without redirection? (F #)

I have this little function that saves me some headaches from dealing with the terrible System.Diagnostics.Process API:

let HiddenExec (command: string, arguments: string) =
    let startInfo = new System.Diagnostics.ProcessStartInfo(command)
    startInfo.Arguments <- arguments
    startInfo.UseShellExecute <- false

    startInfo.RedirectStandardError <- true
    startInfo.RedirectStandardOutput <- true

    use proc = System.Diagnostics.Process.Start(startInfo)
    proc.WaitForExit()
    (proc.ExitCode,proc.StandardOutput.ReadToEnd(),proc.StandardError.ReadToEnd())

This works fine because I get a three-element tuple with the results of exitcode, stdout and stderr.

Now suppose I don’t want to β€œhide” the execution. That is, I want to write a hypothetical, simpler function called Exec. Then the solution is to not redirect stdout / stderr, and we are done:

let Exec (command: string, arguments: string) =
    let startInfo = new System.Diagnostics.ProcessStartInfo(command)
    startInfo.Arguments <- arguments
    startInfo.UseShellExecute <- false

    let proc = System.Diagnostics.Process.Start(startInfo)
    proc.WaitForExit()
    proc.ExitCode

However, it would be nice if I could reorganize these two functions in order to reduce them into one, and simply pass the "hidden" bool flag to it:

let NewExec (command: string, arguments: string, hidden: bool) =

, NewExec(_,_,false) stdout, stderr ( exitCode, ). , (startInfo.RedirectStandardError <- true), proc.StandardOutput.ReadToEnd(), StandardOut has not been redirected or the process hasn't started yet.

, , Console.WriteLine(eachOutput), , stderr stdout , . , .

, ? Process?: (

+4
2

" ".

HiddenExec Exec, .

, :

let ExecWith configureStartInfo returnFromProc (command: string, arguments: string) =
    let startInfo = new System.Diagnostics.ProcessStartInfo(command)
    startInfo.Arguments <- arguments
    startInfo.UseShellExecute <- false

    // parameterize this bit
    configureStartInfo startInfo

    use proc = System.Diagnostics.Process.Start(startInfo)
    proc.WaitForExit()

    // parameterize this bit too
    returnFromProc proc

, , returnFromProc, , .

HiddenExec, 3-, :

/// Specialize ExecWith to redirect the output.
/// Return the exit code and the output and error.
/// Signature: string * string -> int * string * string
let HiddenExec =

    let configureStartInfo (startInfo: System.Diagnostics.ProcessStartInfo) =
        startInfo.RedirectStandardError <- true
        startInfo.RedirectStandardOutput <- true

    let returnFromProc (proc:System.Diagnostics.Process) =       
        (proc.ExitCode,proc.StandardOutput.ReadToEnd(),proc.StandardError.ReadToEnd())

    // partial application -- the command & arguments are passed later
    ExecWith configureStartInfo returnFromProc 

, , : 3- :

val HiddenExec : string * string -> int * string * string

, . HiddenExec :

let HiddenExec (command, arguments) =  // (command, arguments) passed here

    let configureStartInfo ...
    let returnFromProc ...

    ExecWith configureStartInfo returnFromProc (command, arguments) // (command, arguments) passed here

Exec, , :

/// Specialize ExecWith to not redirect the output.
/// Return the exit code.
/// Signature: string * string -> int
let Exec =

    let configureStartInfo _  =
        ()  // ignore the input

    let returnFromProc (proc:System.Diagnostics.Process) = 
        proc.ExitCode    

    ExecWith configureStartInfo returnFromProc

    // alternative version using `ignore` and lambda
    // ExecWith ignore (fun proc -> proc.ExitCode)    

, , , : ExitCode:

val Exec : string * string -> int 
+5

@ Groundoon , :)

# F #:

let private procTimeout = TimeSpan.FromSeconds(float 10)

let Execute (commandWithArguments: string, echo: bool, hidden: bool)
    : int * string * string =

    let outBuilder = new StringBuilder()
    let errBuilder = new StringBuilder()

    use outWaitHandle = new AutoResetEvent(false)
    use errWaitHandle = new AutoResetEvent(false)

    if (echo) then
        Console.WriteLine(commandWithArguments)

    let firstSpaceAt = commandWithArguments.IndexOf(" ")
    let (command, args) =
        if (firstSpaceAt >= 0) then
            (commandWithArguments.Substring(0, firstSpaceAt), commandWithArguments.Substring(firstSpaceAt + 1))
        else
            (commandWithArguments, String.Empty)

    let startInfo = new ProcessStartInfo(command, args)
    startInfo.UseShellExecute <- false
    startInfo.RedirectStandardOutput <- true
    startInfo.RedirectStandardError <- true
    use proc = new Process()
    proc.StartInfo <- startInfo

    let outReceived (e: DataReceivedEventArgs): unit =
        if (e.Data = null) then
            outWaitHandle.Set() |> ignore
        else
            if not (hidden) then
                Console.WriteLine(e.Data)
            outBuilder.AppendLine(e.Data) |> ignore

    let errReceived (e: DataReceivedEventArgs): unit =
        if (e.Data = null) then
            errWaitHandle.Set() |> ignore
        else
            if not (hidden) then
                Console.Error.WriteLine(e.Data)
            errBuilder.AppendLine(e.Data) |> ignore

    proc.OutputDataReceived.Add outReceived
    proc.ErrorDataReceived.Add errReceived

    let exitCode =
        try
            proc.Start() |> ignore
            proc.BeginOutputReadLine()
            proc.BeginErrorReadLine()

            if (proc.WaitForExit(int procTimeout.TotalMilliseconds)) then
                proc.ExitCode
            else
                failwith String.Format("Timeout expired for process '{0}'", commandWithArguments)

        finally
            outWaitHandle.WaitOne(procTimeout) |> ignore
            errWaitHandle.WaitOne(procTimeout) |> ignore

    exitCode,outBuilder.ToString(),errBuilder.ToString()
0

Source: https://habr.com/ru/post/1648375/


All Articles