getopt_long and swift example

Hello,

I'm diving in to Swift and I'm looking to write a small CLI app. I'm looking for a quick example on how to do command line arguments using getopt_long. I really have not found a thing of this so it make me wonder. What is the right way to skin this cat :-)


Thanks,

Charles

Calling

getopt_long
from Swift is a bit tricky. You can get at the command line arguments (using the CommandLine type) but the
getopt_long
API itself has many C-isms that complicate the process of gluing those two parts together. At the end of this post you’ll find an example that uses
getopt
. It works, but if you’re doing a lot of work in this space you might want to use a some sort of third-party package for this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
func parseArguments() -> (noExecute: Bool, printStateOnStop: Bool, trace: Bool, input: [URL])? {
    var noExecute = false
    var printStateOnStop = false
    var trace = false
    repeat {
        let ch = getopt(CommandLine.argc, CommandLine.unsafeArgv, "npto:")
        if ch == -1 {
            break
        }
        switch UnicodeScalar(Int(ch)).flatMap(Character.init) {
            case "n"?:
                noExecute = true
            case "p"?:
                printStateOnStop = true
            case "t"?:
                trace = true
            default:
                return nil
        }
    } while (true)

    guard optind + 1 == CommandLine.argc else {
        return nil
    }
    let input = CommandLine.arguments[Int(optind)..<CommandLine.arguments.count].map { URL(fileURLWithPath: $0) }
    return (noExecute, trace, printStateOnStop: printStateOnStop, input)
}

Thanks, I really appricate it. I have a getopt solution in place. The main reason for asking was it was doing a port from Obj-C / C and I wanted to keep the old getopt_long but as you answered it's tricky. So in the mean time I'll just stick with the getopt solution and revisit it at a later time.


Thanks,

Charles

So in the mean time I'll just stick with the getopt solution and revisit it at a later time.

Fair enough.

I should have mentioned this earlier but please do file a bug requesting that Swift provide a better API for command line parsing. It’s kinda silly that everyone has to reinvent this particular wheel.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

OK, I'll file a bug on this. Thanks for your help, much appricated!


Charles

Because the problem of getopt_long(3) is in the option.name lifetime (if more preciously, the content of buffer pointed by implicit String -> const char* conversion), I managed getopt_long() to work using StaticString with explicit conversion to UnsafePointer<CChar> (which is Swift counterpart of the const char*).


Something like that:


func newCCharPtrFromStaticString(_ str: StaticString) -> UnsafePointer<CChar>
{
    let rp = UnsafeRawPointer(str.utf8Start);
    let rplen = str.utf8CodeUnitCount;
    return rp.bindMemory(to: CChar.self, capacity: rplen);
}

func main() -> Int32
{
    enum OptLongCases: Int32 {
        case h = 0x68;
        case f = 0x66;
        case OPT_FIRST = 256;
        case version;
    };

    let longopts: [option] = [
        option(name: newCCharPtrFromStaticString("help"),      has_arg: no_argument,       flag: nil, val: OptLongCases.h.rawValue),
        option(name: newCCharPtrFromStaticString("version"),   has_arg: no_argument,       flag: nil, val: OptLongCases.version.rawValue),
        option(name: newCCharPtrFromStaticString("file"),      has_arg: required_argument, flag: nil, val: OptLongCases.f.rawValue /* Int32("f".utf8.first!) */),
        option()    // { NULL, NULL, NULL, NULL }
        ];

    var hash: Dictionary<String, String> = [:];

    while (true)
    {
        let opt = getopt_long(CommandLine.argc, CommandLine.unsafeArgv, "hf:", longopts, nil);
        switch (opt)
        {
        case OptLongCases.h.rawValue:
            Help();
            return EXIT_SUCCESS;
        case OptLongCases.version.rawValue:
            Version();
            return EXIT_SUCCESS;
        case OptLongCases.f.rawValue:
            hash["f"] = String(cString: optarg);
        case 0:     // long option with non-NULL .flag field, the value returned there
            break;
        case -1:    // end of options
            break;
        default:
            UseHelp();
            return EXIT_FAILURE;
        }
        if (opt == -1)
        {
            break;
        }
    }

    if let fn = hash["f"] {
        print("-f aka --file = \"\(fn)");
    }
    for u in optind ..< CommandLine.argc {
        let i = Int(u);
        let s = CommandLine.arguments[i];
        print("argv[\(i)] = \"\(s)\"");
    }
    return EXIT_SUCCESS;
}
getopt_long and swift example
 
 
Q