Fsharp: call shell command
ai answer
open System open System.Diagnostics open System.Text type ProcessResult = { ExitCode: int StdOut: string StdErr: string } let executeProcess (processName: string) (processArgs: string) = let psi = ProcessStartInfo(processName, processArgs) psi.UseShellExecute <- false psi.RedirectStandardOutput <- true psi.RedirectStandardError <- true psi.CreateNoWindow <- true use proc = Process.Start(psi) let output = StringBuilder() let error = StringBuilder() proc.OutputDataReceived.Add(fun args -> if not (isNull args.Data) then output.AppendLine(args.Data) |> ignore) proc.ErrorDataReceived.Add(fun args -> if not (isNull args.Data) then error.AppendLine(args.Data) |> ignore) proc.BeginOutputReadLine() proc.BeginErrorReadLine() proc.WaitForExit() { ExitCode = proc.ExitCode StdOut = output.ToString() StdErr = error.ToString() } // s------------------------------ // let result = executeProcess "uptime" "-p" // Linux/macOS // or let result = executeProcess "cmd.exe" "/c dir" // Windows printfn "Exit code: %d" result.ExitCode printfn "Stdout:\n%s" result.StdOut if result.StdErr <> "" then eprintfn "Stderr:\n%s" result.StdErr (* Exit code: 0 Stdout: Volume in drive C is Windows Volume Serial Number is 86F8-588E Directory of c:\Users\xah\.emacs.d\temp 07/29/2026 08:10 AM <DIR> . 07/27/2026 08:56 PM <DIR> .. 07/16/2026 05:52 PM 288 20260716_1752_ac24.js~ 07/16/2026 05:55 PM 967 20260716_1755_19ce.js~ ... *)
How to call shell commands in Fsharp?
How to call shell commands in Fsharp?
ai answer
In F#, you call shell commands (or any external process) using the .NET System.Diagnostics.Process class. This is the standard, built-in approach—no extra packages required.
Basic synchronous example (capture stdout, stderr, and exit code)
open System open System.Diagnostics open System.Text type ProcessResult = { ExitCode: int StdOut: string StdErr: string } let executeProcess (processName: string) (processArgs: string) = let psi = ProcessStartInfo(processName, processArgs) psi.UseShellExecute <- false psi.RedirectStandardOutput <- true psi.RedirectStandardError <- true psi.CreateNoWindow <- true use proc = Process.Start(psi) let output = StringBuilder() let error = StringBuilder() proc.OutputDataReceived.Add(fun args -> if not (isNull args.Data) then output.AppendLine(args.Data) |> ignore) proc.ErrorDataReceived.Add(fun args -> if not (isNull args.Data) then error.AppendLine(args.Data) |> ignore) proc.BeginOutputReadLine() proc.BeginErrorReadLine() proc.WaitForExit() { ExitCode = proc.ExitCode StdOut = output.ToString() StdErr = error.ToString() }
Usage:
let result = executeProcess "uptime" "-p" // Linux/macOS // or let result = executeProcess "cmd.exe" "/c dir" // Windows printfn "Exit code: %d" result.ExitCode printfn "Stdout:\n%s" result.StdOut if result.StdErr <> "" then eprintfn "Stderr:\n%s" result.StdErr
Simpler version (when you mainly care about exit code + full output)
open System.Diagnostics let run (cmd: string) (args: string) = let startInfo = ProcessStartInfo( FileName = cmd, Arguments = args, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true ) use p = Process.Start(startInfo) let stdout = p.StandardOutput.ReadToEnd() let stderr = p.StandardError.ReadToEnd() p.WaitForExit() p.ExitCode, stdout, stderr
Running a full shell command (e.g. with pipes, redirects, etc.)
On Linux/macOS you typically invoke a shell:
// Using bash let result = executeProcess "/bin/bash" "-c \"ls -la | head -5\"" // Or more portably with env let result = executeProcess "/usr/bin/env" "bash -c \"echo Hello from shell\""
On Windows:
let result = executeProcess "cmd.exe" "/c \"dir & echo Done\"" // or let result = executeProcess "powershell.exe" "-Command \"Get-Process | Select-Object -First 5\""
Asynchronous version (recommended for modern code)
open System.Diagnostics open System.Threading.Tasks let executeCommandAsync executable (args: string list) = async { let startInfo = ProcessStartInfo() startInfo.FileName <- executable startInfo.RedirectStandardOutput <- true startInfo.RedirectStandardError <- true startInfo.UseShellExecute <- false startInfo.CreateNoWindow <- true args |> List.iter startInfo.ArgumentList.Add use p = new Process(StartInfo = startInfo) p.Start() |> ignore let! stdoutTask = p.StandardOutput.ReadToEndAsync() |> Async.AwaitTask let! stderrTask = p.StandardError.ReadToEndAsync() |> Async.AwaitTask do! p.WaitForExitAsync() |> Async.AwaitTask return p.ExitCode, stdoutTask, stderrTask }
Important notes
- Set
UseShellExecute = falsewhen you want to redirect stdout/stderr (required for capturing output). - Prefer
ArgumentList.Add(...)(available in modern .NET) over concatenating a singleArgumentsstring—it handles quoting safely. - Always dispose of the
Process(theusekeyword does this). - Check the exit code; non-zero usually indicates failure.
- Working directory, environment variables, and credentials can be set via
ProcessStartInfo.
Higher-level library (optional)
If you prefer a more F#-idiomatic API with computation expressions, consider the Fli library:
dotnet add package Fli
open Fli cli { Shell BASH // or CMD, PWSH, etc. Command "ls -la | head -3" } |> Command.execute |> Output.toText |> printfn "%s"
For most cases the built-in System.Diagnostics.Process approach is sufficient and has zero dependencies.