This is a tiny getopt implementation that closely mirrors Unix (POSIX) getopt(3).
Design goals were to match the behavior of its C namesake with very little code. The former makes it familiar, compatible, and flexible. The latter makes it fine to copy and paste into stand-alone scripts.
Non-goals: built-in help, long options, options after the first non-option, optional option arguments.
Existing solutions did not meet my chosen goals:
Usage example:
set u {[-frv] [-C path] [-p pattern] [file ...]}
while {[getopt argv {C:p:frv} opt arg]} {
if {$opt eq "?"} {
puts stderr "usage: [file tail $::argv0] $u"
exit 1
}
set opts(-$opt) $arg
}Remarks:
Implementation:
proc getopt {argvvar optstring optvar argvar} {
upvar $argvvar argv $optvar opt $argvar arg
set arg [lindex $argv 0] ;# "" if argv empty
set opt [string index $arg 1] ;# "" if arg empty or length 1
if {$opt eq "" || [string index $arg 0] ne "-"} {
return 0 ;# no option
}
set argv [lreplace $argv 0 0] ;# tentative consumption
if {$arg eq "--"} {
return 0 ;# terminate option processing
}
# consume option character
set arg [string replace $arg 0 1] ;# cut off the dash, too
if {$arg ne ""} { ;# not yet exhausted?
set argv [linsert $argv 0 -$arg] ;# put it back for now
}
# check if option is valid
set idx [string first $opt $optstring]
if {$idx == -1 || $opt eq ":"} {
puts stderr "[file tail $::argv0]: unknown option -$opt"
set opt "?"
return 1 ;# can call again for more
}
# handle option arguments
if {[string index $optstring [incr idx]] eq ":"} {
if {$arg eq ""} {
if {[llength $argv] == 0} {
puts stderr "[file tail $::argv0]: -$opt requires an argument"
set opt [expr {[string index $optstring 0] eq ":" ? ":" : "?"}]
}
set arg [lindex $argv 0] ;# use next arg, no-op if empty
}
set argv [lreplace $argv 0 0] ;# consume arg, no-op if empty
} else {
set arg 1 ;# boolean flag
}
return 1
}