[sbron] 7-Dec-2008: There are several pieces of code available (on the wiki, in [tcllib]) for parsing command lines, but they all feel a little awkward to me. For that reason I came up with the code below. It allows the user to specify the allowed command line options in a similar fashion to the [switch] command. Only in this case there's an implied loop that repeats as long as command line options are available. The switch patterns also serve as the specification of which command line options are allowed. A colon at the end of a long option pattern, or after a character in a short option pattern, indicates that the option requires an argument. Command line options can appear in two forms: long options and short options. Long options start with -- and may be specified on the command line as an unambiguous prefix. Short options start with just a single -. Multiple short options may be specified as a single string of option characters prefixed with a -. If one of the characters represents an option that requires an argument, the rest of the string is taken as the argument. An argument of exactly '--' will cause all remaining arguments to be treated as regular arguments, not as options, even if they start with a -. A '-' on its own (usually signifying that input should be taken from standard input instead of a file) will not be considered as an option but as a regular argument. At the end of the loop the ''arglist'' branch is executed with the arguments remaining after all options have been processed. It will do this even if there are no remaining arguments. Two other special branches are available for handling invalid options: ''missing'' and ''unknown''. The missing branch is executed when there is no argument for an option that requires an argument. The unknown branch is executed when an unknown or ambiguous option is encountered. In these last two cases the argument variable will contain the offending option. The ''default'' branch may also be specified as the final branch. It will be executed if one of the special branch situations occurs and the dedicated branch for that situation does not exist. An example of how this proc can be used: ====== getopt flag arg $argv { -h? - --help { # The user wants some help help } -v { # Increase the verbosity level incr verbosity } --verbose: { # Set the verbosity level set verbosity $arg } -V - --version { # Report the version of the program puts "Version: $version" exit 0 } -ptrb:g: { # Various other options set option($flag) $arg } missing { puts stderr "option requires argument: $arg" exit 2 } unknown { puts stderr "unknown or ambiguous option: $arg" exit 2 } arglist { set files $arg } } ====== In the above example, if an option --ver is passed it will be reported to be ambiguous. But --vers is enough to report the version of the program. Allowing option values to be changed between arguments can also easily be achieved by calling getopt in a loop: ====== while {[llength $argv] > 0} { getopt flag arg $argv { -x: - --optionx: { set optionx $arg } default { if {$flag ne "arglist" || [llength $arg] == 0} { usage exit 2 } } } set argv [lassign $arg file] process $file } ====== If called with: '--optionx 42 file1 file2 -x99 file3', this would process files file1 and file2 with optionx set to 42 and file3 with optionx set to 99. But using: '-x 42 file1 -x 88' would produce a usage message because the file to be processed with the last value of optionx is missing. And finally here's the code to accomplish all this: ====== package require Tcl 8.5 proc getopt {optvar argvar list body} { upvar 1 $optvar option $argvar value set arg(missing) [dict create pattern missing argument 0] set arg(unknown) [dict create pattern unknown argument 0] foreach {pat code} $body { switch -glob -- $pat { -- {# end-of-options option} --?*: {# long option requiring an argument set arg([string range $pat 0 end-1]) \ [dict create pattern $pat argument 1] } --?* {# long option without an argument set arg($pat) [dict create pattern $pat argument 0] } -?* {# short options set last ""; foreach c [split [string range $pat 1 end] ""] { if {$c eq ":" && $last ne ""} { dict set arg($last) argument 1 set last "" } else { set arg(-$c) [dict create pattern $pat argument 0] set last -$c } } } } } while {[llength $list]} { set rest [lassign $list opt] # Does it look like an option? if {$opt eq "-" || [string index $opt 0] ne "-"} break # Is it the end-of-options option? if {$opt eq "--"} {set list $rest; break} set option [string range $opt 0 1] set value 1 if {$option eq "--"} { # Long format option if {[info exists arg($opt)]} { set option $opt } elseif {[llength [set match [array names arg $opt*]]] == 1} { set option [lindex $match 0] } else { # Unknown or ambiguous option set value $opt set option unknown } if {[dict get $arg($option) argument]} { if {[llength $rest]} { set rest [lassign $rest value] } else { set value $option set option missing } } } elseif {![info exists arg($option)]} { set value $option set option unknown if {[string length $opt] > 2} { set rest [lreplace $list 0 0 [string replace $opt 1 1]] } } elseif {[dict get $arg($option) argument]} { if {[string length $opt] > 2} { set value [string range $opt 2 end] } elseif {[llength $rest]} { set rest [lassign $rest value] } else { set value $option set option missing } } elseif {[string length $opt] > 2} { set rest [lreplace $list 0 0 [string replace $opt 1 1]] } uplevel 1 [list switch -- [dict get $arg($option) pattern] $body] set list $rest } set option arglist set value $list uplevel 1 [list switch -- arglist $body] } ====== ---- [Category Argument Processing]