MOVED to https://github.com/FullQueueDeveloper/Swish Who wants to use a Ruby script to upload your iOS project? Not me. Let's use Swift.

What's New



support macOS 11 and Swift 5.5 (previously required macOS 12 and Swift 5.6)


Who wants to use Bash or Ruby scripts to maintain your Swift project? Not me. Let's use Swift.

Swish is a Swift task runner that lets you reason about your script in Swift, easily calling shell commands and using their output in your Swift program.

Or when orchestrating a build script, simply redirect all output to the terminal, a log file, or /dev/null.


Bash scripts have gotten us pretty far, but it's difficult reasoning about control flow. And there's no type safety. Many command line tools already have decent interfaces, it's just the control flow of that could use some improvements. Swish solves this by relying on Swift control flow.



Install with Mint

mint install danramteke/swish


git clone https://github.com/danramteke/swish.git
cd swish
swift build -c release

And then add ./swish/.build/release/ to your path.

Getting started

swish --init

Will scaffold a new Swish project in the scripts subdirectory of your current working directory. This is what it will look like.

 +- scripts
      +- Package.swift
      +- Sources/script1/main.swift

Then you can run swish or swish --list to see the current executable targets. Then you can run swish script1 to run the simple sample script.


Regular use

swish <target-name> [arguments] [for] [target]

- <target-name>  The name of the `executableTarget` in the
                            `Package.swift` in the `scripts`
                            subdirectory of the current working
- [arguments] [for] [target]  Arguments passed to the target    

Available commands

    list the available targets

swish --list
    list the available targets

swish --version
    show version and exit

swish --help
    show this message
swish --init
    scaffold a new Swish scripts subdirectory in
    the current directory. The default scripts
    subdirectory is `scripts`

swish --add <name>
    add a new script named <name> by
    creating a file at path `Sources/<name>/main.swift`
    & adding an `.executableTarget` to `Package.swift`

swish --build
    update & build the scripts package, as a convenience.

Writing scripts:

Fetching data from the shell

Here is a simple example, where we ask the shell for the date, formatted as seconds since 1970. We then parse a Foundation.TimeInterval, since it conforms to Codable. Last, we construct a Data, and print it.

import SwishKit
import Foundation

let timeInterval = try sh(TimeInterval.self, "date +%s")
let date = Date(timeIntervalSince1970: timeInterval)
print("The date is \(date).")

A more substantial example might query op or lpass for a secret, or query terraform output for information about our infrastructure, or query avtool for Apple version info of our Xcode project.

Long running scripts

This file might live in scripts/Sources/pre-commit/main.swift. Perhaps we want to run our tests, and confirm that the release build succeeds as well. Perhaps we want to see the output of swift test in our terminal so we can react to it, but we don't really care to immediately see any release build output, happy to send it to a log file.

import SwishKit
import Foundation

try sh(.terminal, "swift test")
try sh(.file("logs/build.log"), "swift build -c release")


Swish adds convenience extensions to Foundation.Process.


Swish makes it easier to construct a Foundation.Process.

init(cmd: String, environment: [String: String] = [:], workingDirectory: String? = nil)


Swish makes it easier to run a Process. The basic method runs the process, and returns whatever is in standard output as a Data?

func runReturningOutput() throws -> Data?

Swish adds some helper methods that build on this. runReturningStringOutput parses the Data as a String and trims the whitespace.

try Process("echo hello").runReturningStringOutput() // returns "hello"

Swish can also parse JSON output. Given a simple struct:

struct Simple: Decodable {
  let name: String

We can parse the output like this:

let simple = try #"echo '{"name": "hello"}"#.runReturningJSONDecodedOutput(as: Simple.self)
print(simple.name) // prints "hello"


Swish offers a protocol ProcessConvertible for making processes. All the above helper methods are on ProcessConvertible.

String conforms to ProcessConvertible, so for example, we can "mkdir -p logs".runRedirectingAllOutput(to: .null) to make a logs directory, discarding the output.


Yes, Swish supports Swift's new async/await. All methods have a corresponding async version.


  • Enable calling command line tools easily from Swift, since Swift's type system is nice.
  • Allow easy variable substitution in shell calls, and what was run in the shell can be announced to the terminal, for easy copy-paste
  • Scripts don't really want to be compiled since then their contents are change frequently. Scripts want to be compiled just-in-time on every run

This package specifically does not try to provide a domain specific language for various tools. For those looking for a DSL, there is Fastlane or Puma.


There is example projects in the demos folder

  • The vapor-demo is a Vapor app. It can be a handful to type out a full docker build command each time. This example is short, but still meaningful. Running swish docker from the demos/vapor-demo directory will build the docker container for this small vapor app.


  • Swift Tools 5.5.0
View More Packages from this Author


Last updated: Mon Aug 22 2022 03:47:47 GMT-0500 (GMT-05:00)