我有这个小函数,可以让我在处理可怕的 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())
这非常有效,因为我得到了包含退出代码、stdout 和 stderr 结果的三个元素的元组。
现在,假设我不想“隐藏”执行。也就是说,我想编写一个假设的、更简单的 Exec 函数。那么解决方案是不重定向 stdout/stderr,我们就完成了:
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
但是,如果我可以重构这两个函数以将它们聚合为一个函数,然后向其传递一个“隐藏” bool 标志,那就太好了:
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
.
始终重定向输出的另一个选项,如果传递的隐藏标志不为 true,则调用 Console.WriteLine(eachOutput)
,但这不是很优雅,因为它会一次性写入缓冲区,而不会按照它们出现的正确顺序在屏幕中的 stdout 行之间插入 stderr。对于长时间运行的进程,它将隐藏增量输出,直到进程完成。
那么这里有什么选择呢?我是否需要诉诸使用 Process
中的该死的事件?类(class)? :(
干杯
最佳答案
我会遵循“参数化所有事物”的原则。
在这种情况下,这意味着找到 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
来指定重定向和三元组返回值,就像您最初所做的那样:
/// 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
关于.net - 如何在不重定向的情况下读取 Process.StandardOutput ? (F#),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38459064/