Skip to content
AgileLogics
6 min read

Scripting with .fsx

Prerequisites: Working with files

F# scripts are a strongly typed alternative to shell, Python, or PowerShell scripts for automation, data fixes, and exploration. One file, one command: dotnet fsi script.fsx.

A practical script

open System
open System.IO

// Create some sample files so the script has something to work on
Directory.CreateDirectory "logs" |> ignore
File.WriteAllLines("logs/app-1.log", [ "INFO start"; "ERROR disk full"; "INFO done" ])
File.WriteAllLines("logs/app-2.log", [ "WARN slow"; "ERROR timeout"; "ERROR timeout" ])

let errorsByFile =
    Directory.GetFiles("logs", "*.log")
    |> Array.sort
    |> Array.map (fun path ->
        let errors =
            File.ReadLines path
            |> Seq.filter (fun line -> line.StartsWith "ERROR")
            |> Seq.length
        Path.GetFileName path, errors)

for (file, count) in errorsByFile do
    printfn "%-10s %d error(s)" file count

let total = errorsByFile |> Array.sumBy snd
if total > 0 then
    eprintfn "Found %d errors" total
    exit 1
app-1.log  1 error(s)
app-2.log  2 error(s)
Found 3 errors
  • exit 1 ends the script with a non-zero exit code, so CI pipelines and shell scripts can detect failure.
  • eprintfn writes to standard error.

Script directives

Directive Does
#r "nuget: Name, 1.2.3" reference a NuGet package (optionally pinned)
#r "path/to/Library.dll" reference a local assembly
#load "Helpers.fsx" include another script (its modules become available)
#i "nuget: https://my-feed/index.json" add a package source
#time "on" print timing information for each evaluation (interactive)

__SOURCE_DIRECTORY__ and __SOURCE_FILE__ give the script's location, and fsi.CommandLineArgs its arguments.

Running external programs

open System.Diagnostics

let run (file: string) (args: string) =
    let psi = ProcessStartInfo(file, args, RedirectStandardOutput = true, UseShellExecute = false)
    use p = Process.Start psi
    let output = p.StandardOutput.ReadToEnd()
    p.WaitForExit()
    p.ExitCode, output.Trim()

let (code, version) = run "dotnet" "--version"
printfn "exit %d, SDK major version %s" code (version.Split('.')[0])
exit 0, SDK major version 10

Libraries such as Fli and CliWrap make this nicer, with pipelines and async support.

Build scripts

Many F# projects keep their build automation in F#: a build.fsx script, or a small console project, using Fake modules (for example, Fake.DotNet.Cli to call dotnet build and dotnet test) — typed, debuggable, and cross-platform, instead of fragile shell scripts.

Notebooks

Polyglot Notebooks in VS Code run F# in notebook cells, with charts (via Plotly.NET) and data tables — useful for data exploration and reports.

Common mistakes

  • Unpinned #r "nuget: ..." references: a new package version can break an old script. Pin versions for scripts you keep.
  • Assuming the current directory is the script's folder — use __SOURCE_DIRECTORY__.