Marin Usalji, creator of Alcatraz, xcpretty and ObjectiveRecord gave a great talk about using Swift for CLI tools at the SLUG meetup. He covered the current state of the language, CLI basics, and what needs to be done.
As usual, video & slides are synchronized. The video is also subtitled. You can find a blog version of the talk (with code samples) below.
This blogpost is adapted from Marin’s own notes.
Since its release, Swift has gained a decent traction as a language for building mobile / desktop apps. This talk will show the other side of Swift, it’s scripting capabilities and how you can use it for building CLI tools.
We will cover CLI basics, status codes, documentation, pipeline, and some real life usages. Looking in the future, can Swift dominate Ruby and bash as a tooling language?
If you were watching Swift from it’s early beginnings, you might have noticed that executing Swift code got significantly simpler.
/Applications/Xcode6-Beta1.app/Contents/Developer/usr/bin/xcrun swift -i script.swift
xcrun swift -i script.swift
swift -i script.swift
It’s noticeable that the core team gives attention to Xcode-less development, and is trying to make Swift a first class CLI language.
‘Scripting’ vs Compiling
When we talk about scripting, we’re mostly referring to interpreted languages. What makes working with these a pleasure is the ability to quickly just open a file and execute it inline, like so:
Fortunately, some languages like Go have set a good example how to achieve the scripting-style workflow, while staying compiled languages. To give you an example, you can execute a Go file with
go run script.go. Go compiler will compile the file implicitly, and execute it in place.
Swift went in the Go direction - you can execute a Swift program inline, without knowing it’s being compiled and running a binary.
# Execute inline - $ swift todos.swift
The best part is that you can still compile a Swift script into a binary and ship it, just like Go programs.
# Compile and run - $ swiftc todos.swift -o todos - chmod +x todos - todos
UNIX looks for the magic comment on the beginning of your script, called Shebang, Hashbang or even pound-bang. It contains a hash, exclamation mark and the interpreter command such as
When a script with a Shebang is run as a program, the program loader parses the rest of the script’s initial line as an interpreter directive.
To make the script executable, we need to give it the right permissions. Command
chmod allows us to change file modes or Access Control Lists.
chmod +x script.swift
If you want to use your Swift program from anywhere, you need to tell your OS where to find it. UNIX-like systems usually look at the PATH variable. Assuming we’ll put the compiled products in
~/bin, we need to prepend it to the standard PATH:
When a program terminates, it exits with a code. Zero usually means a successful exit, and everything else is considered as an error.
If you want to force exit with a code, you can use the built-in
exit() function, e.g.
One of the useful functions is checking the exit status of the last process terminated. It’s usually stored in the
$? variable, but there’s no support for it in Swift yet.
Commands usually take parameters and flags in the same way functions take arguments. Passing them through your shell is pretty easy, but then comes the parsing part inside your script.
It’s generally a bad idea to do this all by hand; Some languages like Ruby have OptionParser built in the standard lib.
By the POSIX standard, flags should have a long and short version. Long flags should start with a double dash, and short ones with only one. For example, command
generate should be able to take
-v and achieve the same thing. The cool thing about abbreviations is that you can chain them without adding dashes, like so:
generate --view --controller should be the same as
generate -vc. (the latter only works in Silicon Valley)
Every CLI tool should have at least the
--help documentation. There’s also
man documentation if you want to document your tool extensively, but most of the smaller tools don’t have it. The cool thing is, if you use a library like OptionParser, it should generate the help documentation for you.
IO is often the most important part of a CLI tool. File and IO modules give you file-system traversal, various file operations including permissions, and very importantly - accessing
You’re mostly either:
- spawning another proccess (
popen), letting it do some work and listening to it’s outputs
- being spawned and transforming the
stdinof your own program
UNIX pipeline is a very flexible way of chaining multiple programs to work together without knowing about each other. A pipe connects
stdout output of the first program with
stdin of another. In the given example
A | B, program A sends to
stdout, and B receives into
stdin. Output from
stderr is always displayed straight into terminal, unless you explicitly redirect it to
/dev/null, or something else.
When talking about transforming - Regular expressions (verbose and yucky in Foundation) - Simple and elegant in Ruby - split, reverse, join, char index, length,…
For a CLI tool to be insanely cool and fun to build, you often connect some crazy parts together. Imagine building an alarm clock that also talks to your coffee machine through it’s API and requests a Lyft after you drink the coffee.
You should be able to use these libraries without effort, like with Ruby gems. Currently there’s CocoaPods, but this setup requires Xcode and you probably don’t want to deal with Xcode.
Remaining Pain Points in Swift
- Option parser
- String operations
- BDD from command line
- Simpler framework loading / linking
- Distributing frameworks