Swift async wrapper for zsh commands sent to Process


This package makes it easy to call zsh command line arguments interfacing with both async and throws in Swift, wrapping up the output as a String

let output:String = try await Process.zsh("swift -version")

If all goes well, the output is a String, for example:

swift-driver version: 1.62.15 Apple Swift version 5.7.2 (swiftlang- clang-1400.0.29.51)
Target: arm64-apple-macosx13.0

If the process does not exit normally, it throws a ProcessError with forwarding the terminationStatus and output.


To make your commands relative to a specific directory, pass in the current working directory in the optional pwd: argument.

import Foundation
import System

let pwd:FilePath = ... 
let output:String = try await Process.zsh("swift -version", pwd:pwd)

Command arguments interpolation

The String literal you input into the .zsh( ) method should be exactly what you would type into the command line. In cases where you want to interpolate other Strings as arguments which need escaping, the ZshCommand interpolation will do that for you, leaving other types intact.

So the String argument this example:

let commitMessage:String = "WIP.  I'm still \"testing\" it."
let output = Process.zsh("git commit -m \(commitMessage)")

is escaped to produce the same output as if you had typed:

let output = Process.zsh("git commit -m WIP.\\ \\ I\\'m\\ still\\ \\\"testing\\\"\\ it.")

While other variables are passed through as is:

let level:Int = 5
let output = Process.zsh("echo \(level)")

In this example, the output would be 5.

Verbatim strings

If your string already has proper escaping, you can turn off the escaping by using the argument label verbatim:.

let alreadyEscapedString:String = "WIP.\\ \\ I\\'m\\ still\\ \\\"testing\\\"\\ it."
let output = Process.zsh("git commit -m \(verbatim:alreadyEscapedString)")


For the special case of booleans, many different apps interpret the format differently, so there is a BoolFormat option available

let shouldIStay:Bool = false
let output = Process.zsh("echo \(shouldIStay, format:.integer)")

produces 0. See ZshInterpolation.BoolFormat for more options.


I may be able to relax the version restrictions and support earlier versions by about a year.

I’ve tested only the most basic strings for escaping support. There are many complexities of argument formatting which escape me.

Should more complex objects also receive special handling? And perhaps only recognized POD types, like ints and floats should get no escaping?

Is there a way to send the arguments directly, without requiring us to actually do the interpolation?

Should both standardoutput and standard error both be sent into the same pipe?

Should we add support for interactive streaming?


View Github