Swift at the Command Line

The topic of command-line Swift popped up on the swift-language mail list today, and it occurred to me that it might be helpful to share my little starter skeleton. The following pulls in the base app name (for usage() or whatever), and polls the dashed arguments and their associated values. (Update: see below about scripting, via Practical Swift.)

import Foundation

let arguments = NSProcessInfo.processInfo().arguments as NSArray
let argv0 = arguments[0] as String
let appName = argv0.lastPathComponent

let predicate = NSPredicate(format:"SELF beginswith '-'", argumentArray:nil)
let dashedArguments : NSString[] = arguments.filteredArrayUsingPredicate(predicate) as NSString[]
// In retrospect, I could have transformed these two lines into:
// let dashedArguments = (arguments as Array).filter({$0.hasPrefix("-")}) as NSString[]

println("App: \(appName)")
for argument : NSString in dashedArguments
{
    let key = argument.substringFromIndex(1)
    let value : AnyObject? = NSUserDefaults.standardUserDefaults().valueForKey(key)
    println("    \(argument) \(value)")
}

What’s interesting is how well Swift works with command line for me. For the last few years, I’ve been using the Catfish_Man patented block dictionaries approach. This enables you to use string keys instead of endless isEqualToString comparisons, with each case stored in a void block.

for (NSString *actualArgument in dashedArguments)
{
    NSString *argument = actualArgument;
    if (equivalences[actualArgument]) argument = equivalences[actualArgument];
    NSString *argumentValue = DEFAULT_OBJ([actualArgument substringFromIndex:1]);

    UtilityBlock block = @{
        @"-help" : ^{
           usage(name);
        },
        @"-message" : ^{
            dict[@"message"] = argumentValue;
            shouldContinue = YES;
        },
    }[argument];

    if (block) block();
}

Because Swift already has object/string-based switch statements, my Swift-based command-line code is ending up cleaner and more succinct than the Objective-C version.

Update:

I had no idea Swift could be used as a scripting language as well. Practical Swift has this write-up.  Here’s a simple example:

#!/usr/bin/env xcrun swift -i

println("This is a swift script!")
println("Arguments: \(Process.arguments)")

To run it, call ./myScriptName.swift -- a b c d at the command line, using whatever name you saved the file to. The double-hyphen is required to pass arguments to the script. It’s pretty easy to bypass if you embed this in a shell script, e.g.

#! /bin/bash
swiftFile="/Users/ericasadun/myScriptName.swift"
cmd="$swiftFile -- $@"
echo $cmd
$cmd

Once you do that, you can call the bash script with normal arguments and will propagate on to the swift file with the proper format. Of course, by that point, you’ll probably want to either write the cmd line app  using Xcode or just write it directly in bash/perl/whatever.

One Comment

  • It’s worth pointing out that invoking a script run with `xcrun swift -i` without actually providing any arguments past the — ends up providing the entire set of compiler arguments to the script instead.

    Also, your bash script won’t handle arguments with spaces properly. Try something like

    echo “$swiftFile — $@”
    $swiftFile — “$@”