Shell scripting in Swift

What's New


This 2.0 release does not profoundly change the API of Shwift or Script. It does remove a (likely unused) public API to read a value from an Environment.

As an added bonus, Shwift is cleaned up for Swift 5.7 and much of the documentation has been improved.



Shwift is a package which provides tools for shell scripting in Swift.

For example, you can write the following swift code to achieve echo Foo Bar | sed s/Bar/Baz/:

try await echo("Foo", "Bar") | sed("s/Bar/Baz/")

While a bit more verbose, this is natively integrated into Swift and utilizes Swift's concurrency APIs. As a result, interacting with command-line tools becomes very natural, and you can do things like echo("Foo", "Bar") | map { $0.replacingOccurences(of: "Bar", with: "Baz" }. We've worked very hard to make the performance of Shwift analogous with the command line. So if you execute the line try await cat("/dev/urandom") | xxd() | head -n2, You won't read any more from /dev/urandom than if you executed the analogous command in the terminal.

The Script module provides API that is as similar as possible to the terminal, but expressed in Swift. It leverages swift-argument-parser and is potimized for writing shell-script-like programs. Here is an example of a simple program you can write (a more detailed example can be found in the ScriptExample target):

import Script

@main struct Main: Script {

  func run() async throws {
     Declare the executables first so that we fail fast if one is missing.

     We could also instead use the `execute("executable", ...)` form to resolve executables at invocation time.
    let echo = try await executable(named: "echo")

    try await echo("Foo", "Bar") | map { $0.replacingOccurrences(of: "Bar", with: "Baz") }

Script is implemented using the Shwift module, which implements the core functionality needed to call command-line tools and process their output. This module can be used directly if you want to interact with command-line tools in a more complex program. For example, you could implement a server which may call a command-line tool in response to an HTTP request.

Shwift is more explicit about exactly what is being executed. You have a Shwift.Context which you can use to manage the lifetime of resources used by Shwift (for example, closing the Shwift.Context once it is no longer necessary). It also provides Builtin, which is a namespace for core functionality that is used to implement higher level Swift functions for interacting with command line tools in Script, like map and reduce.

Shwift is build on top of swift-nio and as a result aims to be completely non-blocking, and thus suitable for use Swift programs which make extensive use of Swift's concurrency features, such as servers.

Shwift also provides its own Process type. This type has a different API from Foundation.Process and embraces Swift's concurrency features, which the Foundation.Process API predates. It also works around a nasty Foundation bug on Linux which can result in leaked file descriptors or deadlocks in concurrently-executing code.


  • Swift Tools 5.5.0
View More Packages from this Author


Last updated: Tue Jan 10 2023 00:09:33 GMT-0500 (GMT-05:00)