loading . . . Introduction to the Swift Subprocess Package The Swift language groupâs Subprocess package lets you spawn processes in Swift apps. The package makes it easier to run Terminal commands and launch command-line programs from Swift apps.
## Launching a program
Call the `run` function to launch a process. In most cases a process will be a Terminal command, a shell script, or a command-line program.
The `run` function is asynchronous so you must add `await` when calling `run`. The function can throw errors so you should wrap the call in a `do-catch` block to handle errors.
If the program to run is installed on the userâs computer, use the `.name` parameter and supply the name of the program to run. You must also supply an `output` argument to specify how to store the output.
The following code runs the `ls` command and collects its output in a string:
do {
let result = try await run(.name("ls"),
output: .string(limit: 2048))
} catch {
print(error)
}
Use the `standardOutput` property from the result to access the programâs output.
## Launching a program from a Mac app bundle
If you bundle a command-line program with a Mac Swift app, you canât use `.name` to run the program because the program isnât installed on the userâs Mac. You must use the `.path` parameter and supply a path to the programâs location. If you have a URL for the program, use its `path` property as the path.
The following code runs a program in the app bundle named `MyProgram`:
var executableLocation: FilePath
let bundle = Bundle.main
let resourceFolder = bundle.resourceURL
let executableURL = resourceFolder?.appendingPathComponent("MyProgram")
executableLocation = FilePath(executableURL?.path(percentEncoded: false) ?? "/")
do {
let result = try await run(.path(executableLocation),
output: .string(limit: 2048))
} catch {
print(error)
}
## Passing arguments
Many Terminal commands and command-line programs take arguments. To pass these arguments to the `run` function, add an `arguments` parameter to the `run` function and supply the arguments.
arguments: ["arg1", "arg2", "arg3"]
The Subprocess package has an `Arguments` data structure for the arguments. If you create a variable to hold a programâs arguments, make sure its data type is `Arguments` and not an array of strings.
## Setting the working directory
If you need to set the working directory for a program, add a `workingDirectory` argument to the `run` function and supply the working directory.
The `workingDirectory` argument takes a `FilePath` instance, not a URL. If you have a URL, use the `URL` structâs `path` function to get a file path.
var currentFolder: URL
var workingDirectory: FilePath
workingDirectory = FilePath(currentFolder.path(percentEncoded: false))
## Output options
The examples so far have returned the output as a string. You must specify a length limit for the string. Make sure you set a high enough limit. If the outputâs length exceeds the limit, the `run` function returns no output.
Other output options include the following:
* A `Data` object
* A file descriptor
* An array of bytes
## Returning error messages
If you need to return an error message if the program runs unsuccessfully, add an `error` argument to the `run` function. The data type for the error message can be one of the output option types: string, `Data` object, file descriptor, or array of bytes.
error: .string(limit: 2048)
Use the `standardError` property from the result to access the error message.
## Supplying input
If you need to supply input to a program, add an `input` argument to the `run` function. You can supply the following kinds of input:
* A string
* A `Data` object
* A file descriptor
* An array of bytes
* A data sequence
* An async data sequence
// content contains the input string
input: .string(content)
## Check if a program ran successfully
To see if the program ran successfully, check the `terminationStatus` property of the result. The `isSuccess` property returns true if the program ran successfully.
if result.terminationStatus.isSuccess {
// Success
} else {
// Failure
}
## A more complex example
The following function runs the Jujutsu version control system command `jj bookmark list`:
func runListBookmarksCommand(repoFolder: URL) async -> Result<String, JujutsuError> {
do {
let result = try await run(
.name("jj"),
arguments: ["bookmark", "list"],
workingDirectory: FilePath(repoFolder.path(percentEncoded: false)),
output: .string(limit: 8192),
error: .string(limit: 8192)
)
if result.terminationStatus.isSuccess {
return .success(result.standardOutput ?? "")
} else {
return .failure(JujutsuError(message: result.standardError ?? "Error"))
}
} catch {
return .failure(JujutsuError(message: error.localizedDescription))
}
}
struct JujutsuError: Error, Equatable {
var message: String = ""
}
## Related reading
* Subprocess GitHub Page
* Automate all the things with Swift Subprocess
* Moving from Process to Subprocess
https://swiftdevjournal.com/posts/subprocess/