Subprocess
Subprocess copied to clipboard
Swift library for macOS providing interfaces for both synchronous and asynchronous process execution
Subprocess
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.
-
Usage
-
Subprocess Class
- Command Input - Data, Text, File
- Command Output - Data, Text, Decodable JSON object, Decodable property list object
-
Subprocess Class
-
Installation
- SwiftPM
- Cocoapods
- Carthage
Full Documentation
Usage
Subprocess Class
The Subprocess
class can be used for command execution.
Command Input
Input for data
let inputData = Data("hello world".utf8)
let data = try await Subprocess.data(for: ["/usr/bin/grep", "hello"], standardInput: inputData)
Input for text
let data = try await Subprocess.data(for: ["/usr/bin/grep", "hello"], standardInput: "hello world")
Input for file URL
let data = try await Subprocess.data(for: ["/usr/bin/grep", "foo"], standardInput: URL(filePath: "/path/to/input/file"))
Command Output
Output as Data
let data = try await Subprocess.data(for: ["/usr/bin/sw_vers"])
Output as String
let string = try await Subprocess.string(for: ["/usr/bin/sw_vers"])
Output as decodable object from JSON
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())
Output as decodable object from Property List
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())
Output mapped to other type
let enabled = try await Subprocess(["/usr/bin/csrutil", "status"]).run().standardOutput.lines.first(where: { $0.contains("enabled") } ) != nil
Output options
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()]
Handling output as it is read
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
}
}
}
Handling output on termination
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
}
Closure based callbacks
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.
})
Handing output on termination with a closure
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
}
Installation
SwiftPM
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
]
)
Cocoapods
pod 'Subprocess'
Carthage
github 'jamf/Subprocess'