One of the first things that intrigued me with the ease of writing code interactively in the Xcode Playground was the feasibility of using Swift as a possible replacement or supplement to traditional shell scripts. When Chris Lattner (one of the founders of the language at Apple) posted the following on Twitter, I knew I was on to something:
Of course Swift supports #! scripts, you can immediately execute a Swift script with "xcrun Swift -i".
Why would you want to do this? Well, first, Swift is quite easy to build fully functional apps very quickly and efficiently, all while maintaining the full power of Foundation. Swift discards the old syntactical chrome of Objective-C (or more accurately C), particularly the whole requiring of a main() method as an entry point to your program. Combined with the ability to actually run an uncompiled Swift file directly from the command line dramatically reduces the friction of using a complete, powerful, system-level language where only simple, barebones scripts would operate before. Swift can bridge the gap between traditional shell scripts and full blown applications.
One of the first things I started tinkering around with when I got home was making this a reality. It's actually not quite as simple as the tweet, but it definitely is quite doable, and opens up a whole new realm of possible opportunities from the command line. So, let's make this happen.
The first step is ensuring that you have the appropriate Xcode selected. From the terminal, you can determine your currently selected Xcode:
You are likely still pointed to the original Xcode 5 install that does not include Swift. Unless the response looks something like this: /Applications/Xcode6-Beta.app/Contents/Developer, you need to switch over to the new beta:
sudo xcode-select --switch /Applications/Xcode6-Beta.app
The location above presumes you placed the Xcode 6 Beta in your applications folder. If you chose somewhere else or renamed the app, you'll need to modify accordingly.
At this point, you can actually run a Swift file directly from the command line. We'll show that as well, but we really want to run our file without having to actually type out the xcrun command. Let's go ahead and create a simple Swift application. Open up your favorite editor and create a Swift file (e.g. HelloWorld.swift) with the following first line hashbang to instruct the shell on how to handle what follows:
#!/usr/bin/xcrun swift -i
Don't forget to modify the permissions of your newly created shell script. By default, the script will not be marked as executable. A simple call to chmod will fix this:
chmod +rx # adds read and execute for everyone
or my preferred syntax:
chmod 755 # read/write/execute for owner and read and execute for everyone else
While you could now type the following from the command line to run your program:
/usr/bin/xcrun swift -i HelloWorld.swift
...there is a much simpler way. Due to the hashbang line at the top, you should be able to just type HelloWorld.swift (or ./HelloWorld.swift depending on how your path environment variable is setup) and see the traditional welcoming first words of application life.
Without the traditional main method, you don't have apparent access to the command line arguments. These are made available through the
C_ARGV constants. You can iterate over
C_ARGV or address specific arguments directly much like a traditional Swift array.
While this is exciting, we still have one small hurdle. If you attempt to import Foundation, allowing access to the file system or environmental variables or a whole slew of other functionality, you'll receive an odd error. We need to let xcrun know where the SDK resides. If you run your Swift file manually, you can use the following:
/usr/bin/xcrun swift -i -sdk $(xcrun --show-sdk-path --sdk macosx)
Unfortunately, it doesn't appear that the substitution works appropriately when used in the hashbang portion of a shell script. I was forced to paste the results from the 2nd xcrun manually. There may be a better way that I'm unware of — until then, replace the first line of your script with the following:
#!/usr/bin/xcrun swift -i -sdk /Applications/Xcode6-Beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk
Now you can access all the goodies Apple provides with a simple import, allowing you to do something like the following:
let user = NSProcessInfo.processInfo().environment.objectForKey("USER") as String
println("Hello World, this is \(user.uppercaseString)")
let currentPath = NSFileManager.defaultManager().currentDirectoryPath
println("The current directory is \(currentPath)")
let contentsAtPath = NSFileManager.defaultManager().contentsOfDirectoryAtPath(currentPath, error: nil) as String
for contentItem in contentsAtPath
Obviously, this doesn't really do much useful, but it hopefully triggers some ideas and possibility of the many things you could do. With a small framework to simplify some of the traditional functionality often used within command line shell scripts, Swift could quickly become the go-to tool for developing everything from simple scripts to full blown command-line apps. Go forth and code!