https://github.com/jamf/subprocess
Swift library for macOS providing interfaces for both synchronous and asynchronous process execution
https://github.com/jamf/subprocess
carthage cocoapods macos swift swift-package-manager
Last synced: 8 months ago
JSON representation
Swift library for macOS providing interfaces for both synchronous and asynchronous process execution
- Host: GitHub
- URL: https://github.com/jamf/subprocess
- Owner: jamf
- License: mit
- Created: 2020-02-24T16:48:06.000Z (over 6 years ago)
- Default Branch: main
- Last Pushed: 2025-03-19T17:56:20.000Z (over 1 year ago)
- Last Synced: 2025-08-09T01:52:12.767Z (11 months ago)
- Topics: carthage, cocoapods, macos, swift, swift-package-manager
- Language: Swift
- Homepage:
- Size: 203 KB
- Stars: 37
- Watchers: 18
- Forks: 10
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Codeowners: CODEOWNERS
Awesome Lists containing this project
README
# Subprocess
[](http://mit-license.org)

[](https://cocoapods.org/pods/Subprocess)
[](https://developer.apple.com/macos)
[](https://developer.apple.com/swift)
[](https://github.com/Carthage/Carthage)
[](https://swift.org/package-manager)
[](https://engineering.jamf.com/Subprocess/documentation/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](#usage)
- [Subprocess Class](#subprocess-class)
- [Command Input](#command-input) - [Data](#input-for-data), [Text](#input-for-text), [File](#input-for-file-url)
- [Command Output](#command-output) - [Data](#output-as-data), [Text](#output-as-string), [Decodable JSON object](#output-as-decodable-object-from-json), [Decodable property list object](#output-as-decodable-object-from-property-list)
- [Installation](#installation)
- [SwiftPM](#swiftpm)
- [Cocoapods](#cocoapods)
- [Carthage](#carthage)
[Full Documentation](./docs/index.html)
# Usage
### Subprocess Class
The `Subprocess` class can be used for command execution.
#### Command Input
###### Input for data
```swift
let inputData = Data("hello world".utf8)
let data = try await Subprocess.data(for: ["/usr/bin/grep", "hello"], standardInput: inputData)
```
###### Input for text
```swift
let data = try await Subprocess.data(for: ["/usr/bin/grep", "hello"], standardInput: "hello world")
```
###### Input for file URL
```swift
let data = try await Subprocess.data(for: ["/usr/bin/grep", "foo"], standardInput: URL(filePath: "/path/to/input/file"))
```
#### Command Output
###### Output as Data
```swift
let data = try await Subprocess.data(for: ["/usr/bin/sw_vers"])
```
###### Output as String
```swift
let string = try await Subprocess.string(for: ["/usr/bin/sw_vers"])
```
###### Output as decodable object from JSON
```swift
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
```swift
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
```swift
let enabled = try await Subprocess(["/usr/bin/csrutil", "status"]).run().standardOutput.lines.first(where: { $0.contains("enabled") } ) != nil
```
###### Output options
```swift
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
```swift
let (stream, input) = {
var input: AsyncStream.Continuation!
let stream: AsyncStream = 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
```swift
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
```swift
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
```swift
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
```swift
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: "",
dependencies: [
// other dependencies
.product(name: "Subprocess"),
]),
// other targets
]
)
```
### Cocoapods
```ruby
pod 'Subprocess'
```
### Carthage
```ruby
github 'jamf/Subprocess'
```