Subprocess is a Swift library for macOS providing interfaces for both synchronous and asynchronous process execution. SubprocessMocks can be used in unit tests for quick and highly customizable mocking and verification of Subprocess usage.
The Subprocess
class can be used for command execution.
let inputData = Data("hello world".utf8)
let data = try await Subprocess.data(for: ["/usr/bin/grep", "hello"], standardInput: inputData)
let data = try await Subprocess.data(for: ["/usr/bin/grep", "hello"], standardInput: "hello world")
let data = try await Subprocess.data(for: ["/usr/bin/grep", "foo"], standardInput: URL(filePath: "/path/to/input/file"))
let data = try await Subprocess.data(for: ["/usr/bin/sw_vers"])
let string = try await Subprocess.string(for: ["/usr/bin/sw_vers"])
struct LogMessage: Codable {
var subsystem: String
var category: String
var machTimestamp: UInt64
}
let result: [LogMessage] = try await Subprocess.value(for: ["/usr/bin/log", "show", "--style", "json", "--last", "30s"], decoder: JSONDecoder())
struct SystemVersion: Codable {
enum CodingKeys: String, CodingKey {
case version = "ProductVersion"
}
var version: String
}
let result: SystemVersion = try await Subprocess.value(for: ["/bin/cat", "/System/Library/CoreServices/SystemVersion.plist"], decoder: PropertyListDecoder())
let enabled = try await Subprocess(["/usr/bin/csrutil", "status"]).run().standardOutput.lines.first(where: { $0.contains("enabled") } ) != nil
let errorText = try await Subprocess.string(for: ["/usr/bin/cat", "/non/existent/file.txt"], options: .returnStandardError)
let outputText = try await Subprocess.string(for: ["/usr/bin/sw_vers"])
async let (standardOutput, standardError, _) = try Subprocess(["/usr/bin/csrutil", "status"]).run()
let combinedOutput = try await [standardOutput.string(), standardError.string()]
let (stream, input) = {
var input: AsyncStream<UInt8>.Continuation!
let stream: AsyncStream<UInt8> = AsyncStream { continuation in
input = continuation
}
return (stream, input!)
}()
let subprocess = Subprocess(["/bin/cat"])
let (standardOutput, _, waitForExit) = try subprocess.run(standardInput: stream)
input.yield("hello\n")
Task {
for await line in standardOutput.lines {
switch line {
case "hello":
input.yield("world\n")
case "world":
input.yield("and\nuniverse")
input.finish()
case "universe":
await waitForExit()
break
default:
continue
}
}
}
let process = Subprocess(["/usr/bin/csrutil", "status"])
let (standardOutput, standardError, waitForExit) = try process.run()
async let (stdout, stderr) = (standardOutput, standardError)
let combinedOutput = await [stdout.data(), stderr.data()]
await waitForExit()
if process.exitCode == 0 {
// Do something with output data
} else {
// Handle failure
}
let command: [String] = ...
let process = Subprocess(command)
nonisolated(unsafe) var outputData: Data?
nonisolated(unsafe) var errorData: Data?
// The outputHandler and errorHandler are invoked serially
try process.launch(outputHandler: { data in
// Handle new data read from stdout
outputData = data
}, errorHandler: { data in
// Handle new data read from stderr
errorData = data
}, terminationHandler: { process in
// Handle process termination, all scheduled calls to
// the outputHandler and errorHandler are guaranteed to
// have completed.
})
let command: [String] = ...
let process = Subprocess(command)
try process.launch { (process, outputData, errorData) in
if process.exitCode == 0 {
// Do something with output data
} else {
// Handle failure
}
let package = Package(
// name, platforms, products, etc.
dependencies: [
// other dependencies
.package(url: "https://github.com/jamf/Subprocess.git", .upToNextMajor(from: "3.0.0")),
],
targets: [
.target(name: "<target>",
dependencies: [
// other dependencies
.product(name: "Subprocess"),
]),
// other targets
]
)
pod 'Subprocess'
github 'jamf/Subprocess'