Swift. The general-purpose programming language designed for safety, performance, and ease of use. This was the sales pitch, but the language has consistently struggled to break out of the prison of the mobile niche, towards other mainstream workflows.
Scripting is one of those use cases that has theoretically been possible in Swift since forever, but is so painful to work with, it’s functionally useless, even for trivial workloads.
Until today.
Kind of.
Today we’re going to look at the brand-new, hot-off-the-press, seriously-it-only-got-released-this-Friday swift-subprocess. It’s a new package that promises to solve some of the chronic, incandescent pain points that Swifty scriptwriters were living with.
Today, we’re going to look at the weird world of scripting:
What is scripting? Why does Swift scripting suck?
How can we use swift-subprocess to automate stuff in Swift?
(because apparently I’m an influencer now) what do I really think?
If you’re not sure about upgrading yet, consider how much of your life you can automate if you got the hang of Swift scripts. Appraise the value of your time. Ponder whether you’ll save more than $12 worth of time in the first month. Perhaps the premium subscription will have already paid for itself!
A primer on scripting
A script is just a text file that gets executed directly by an interpreter. They are perfect for creating “glue” code that stitches different operations and programs together.
In UNIX systems, you’ll be familiar with tools like ls, cat, pwd, echo, and mv. These are actually tiny executable utilities installed on your system, such as /bin/ls.
Bash scripts seamlessly interact with these utilities because the execution environment is specifically designed to orchestrate processes. In (massively over-) simplified terms, the UNIX shell is a loop that reads a command, spawns a process, executes said command, and waits for it to complete.
The shell “just works” because it spawns these child processes, executes commands (like ls, curl, and ffmpeg) in the child process, then pipes results back into the parent’s execution environment automatically, without you needing to think about it.
Swift Scripting
Swift is a strongly-typed, statically-compiled language. So, exactly what you don’t want when you’re running a script. Since early Swift, we’ve been able to run ‘scripts’ by throwing a shebang atop of our .swift file:
#!/usr/bin/swiftYou can run these swift scripts exactly like a regular shell script:
./my_script.swiftSo what’s the problem?
Right, I’ll try not to go off on one. But first off, while this looks and sounds like a run-of-the-mill script, shebang and all, it smells off. Because it’s not a script at all. The Swift code is compiled and executed, assuming all the corresponding overhead.
But moreso, the UNIX shell has spoiled us.
Process forking and result piping seems to come free in Bash because it’s cleverly abstracted away from us. Swift simply hasn’t been built to work like that.
Therefore, even for something as trivial as ls, to list files in a directory, we’re forced to invoke boilerplate Foundation APIs, explicitly forking child processes and manually piping the stdout back into our parent process.
This verbosity isn’t laziness on the part of the Swift language developers: process management and piping semantics just work differently between a Windows server, a MacBook Pro, and an embedded IoT device.
A general-purpose cross-platform programming language like Swift is just subject to different design constraints than an OS-linked execution environment like the UNIX shell.
swift-subprocess
swift-subprocess was pitched as part of the swift-foundation project, and released for the first time this week (as of the time of writing, the first week of September 2025).
It was proposed with 2 goals in mind:
Improve the ergonomics of Swift process creation and result piping.
To open scripts up to modern language semantics like async/await.
It's fundamentally built around the run function.
The executable can be referenced either via the full file URL, or more conveniently using something like .name(“ls”) to directly fetch it from $PATH to allow for more Bash-y ergonomics.
There’s several overloads of run, including a closure-based version that streams the subprocess output as an AsyncSequence of data.
swift-subprocess calls into low-level platform APIs, such as posix_spawn on UNIX systems, to achieve portability from the same API across all platforms.
For a primer on how the swift-foundation uses platform APIs to win cross-platform portability, check out my recent deep-dive on Data:
Simple Subprocess Workflows
Here's the same ls script from before, ported over to use the new swift-subprocess:
Looks great, huh?
This is the beautiful dream woven by the original swift-subprocess proposal.










