#!/usr/bin/tclsh #****************************************************************************** # getopts # # Parse the options from argvret into the array optsret. Optstr is a string # of valid option letters, with a colon following each non-boolean option # letter (each option which takes an option argument). # # Options start with "-", except for the non-options "--", which is skipped # over, and "-". Specifying the bundled option "-" is not allowed. Option # processing terminates upon encountering the first non-option. Multiple # options can be bundled into a single argument. The value of non-boolean # options is taken from the remainder of the argument or the next argument # if the remainder is empty. # # On error, argvret is not modified, optsret(error) contains an error # message, optsret(opt) contains the offending option, and -1 is returned. # # On success, the parsed options are removed from argvret, optsret(error) # and optsret(opt) contain an empty string, and 0 is returned. For each # boolean option, the corresponding element of optsret is set to 0 (false) # or 1 (true). For non-boolean options, the corresponding element of # optsret contains whatever value was specified. For non-boolean options # which are not specified, the corresponding element of optsret is not # created and/or modified. proc getopts { optstr argvret optsret } { upvar $argvret argv upvar $optsret opts # initialize opts array with all boolean options set to 0 (false) set opts(error) [ set opts(opt) "" ] set opts_list [ split $optstr "" ] for { set idx 0 } { $idx < [ llength $opts_list ] } { incr idx } { if {[ lindex $opts_list [ expr $idx+1 ]] == ":" } { incr idx } else { set opts([ lindex $opts_list $idx ]) 0 } } set arg_idx 0 while { $arg_idx < [ llength $argv ]} { set curr_arg [ lindex $argv $arg_idx ] if {[ string index $curr_arg 0 ] != "-" } { break } if { $curr_arg == "-" } { break } if { $curr_arg == "--" } { incr arg_idx; break } incr arg_idx set ch_idx 1 while {1} { set opt_ch [ string index $curr_arg $ch_idx ] if { $opt_ch == "" } { break } incr ch_idx if { $opt_ch == "-" } { set opts(error) "invalid option -" set opts(opt) "-" return -1 } set pos [ string first $opt_ch $optstr ] if { $pos < 0 || ( $opt_ch == ":" && $pos > 0 )} { set opts(error) "invalid option $opt_ch" set opts(opt) $opt_ch return -1 } if {[ string index $optstr [ incr pos ]] != ":" } { set opts($opt_ch) 1 continue } set optarg [ string range $curr_arg $ch_idx end ] if { $optarg == "" } { if { $arg_idx >= [ llength $argv ]} { set opts(error) "missing argument for option $opt_ch" set opts(opt) $opt_ch return -1 } set optarg [ lindex $argv $arg_idx ] incr arg_idx } set opts($opt_ch) $optarg break } } set argv [ lrange $argv $arg_idx end ] return 0 } #****************************************************************************** # getopt # # This function can be used to build getopts, but can also be used for # different semantics, such as handling each occurrence of an option # independently (e.g. "-a -a" handled differently than "-a"). # # Parse the next option from argv and return it in optret and argret. # Optstr is a string of valid option letters, with a colon following each # non-boolean option letter (each option which takes an option argument). # # Options start with "-", except for the non-options "--", which is skipped # over, and "-". Specifying the bundled option "-" is not allowed. Option # processing terminates upon encountering the first non-option. Multiple # options can be bundled into a single argument. The value of non-boolean # options is taken from the remainder of the argument or the next argument # if the remainder is empty. # # On error, argret contains an error message, optret contains the offending # option, and -1 is returned. If no more options are found, 0 is returned # and stateret(idx) contains the index of the first non-option. # # If an option is found, optret contains the option letter, argret contains # the option value (1 for boolean options), and 1 is returned. The parsing # state is returned in stateret, and should not be modified by the caller. proc getopt { optstr argv stateret optret argret } { upvar $stateret state upvar $optret opt_ch upvar $argret optarg if { ! [ info exists state(idx) ]} { set state(idx) 0 set state(idxc) 1 } while { $state(idx) < [ llength $argv ]} { set nextarg [ lindex $argv $state(idx) ] if {[ string index $nextarg 0 ] != "-" } { return 0 } if { $nextarg == "-" } { return 0 } if { $nextarg == "--" } { incr state(idx) return 0 } set opt_ch [ string index $nextarg $state(idxc) ] if { $opt_ch == "" } { incr state(idx) set state(idxc) 1 continue } if { $opt_ch == "-" } { set optarg "invalid option -" return -1 } incr state(idxc) set pos [ string first $opt_ch $optstr ] if { $pos < 0 || ( $opt_ch == ":" && $pos > 0 )} { set optarg "invalid option $opt_ch" return -1 } if {[ string index $optstr [ incr pos ]] != ":" } { set optarg 1 return 1 } set optarg [ string range $nextarg $state(idxc) end ] if { $optarg == "" } { if {[ incr state(idx) ] >= [ llength $argv ]} { set optarg "missing argument for option $opt_ch" return -1 } set optarg [ lindex $argv $state(idx) ] } incr state(idx) set state(idxc) 1 return 1 } return 0 } #****************************************************************************** # getopts2 -- getopts layered on top of getopt # # Parse the options from argvret into the array optsret. Optstr is a string # of valid option letters, with a colon following each non-boolean option # letter (each option which takes an option argument). # # Options start with "-", except for the non-options "--", which is skipped # over, and "-". Specifying the bundled option "-" is not allowed. Option # processing terminates upon encountering the first non-option. Multiple # options can be bundled into a single argument. The value of non-boolean # options is taken from the remainder of the argument or the next argument # if the remainder is empty. # # On error, argvret is not modified, optsret(error) contains an error # message, optsret(opt) contains the offending option, and -1 is returned. # # On success, the parsed options are removed from argvret, optsret(error) # and optsret(opt) contain an empty string, and 0 is returned. For each # boolean option, the corresponding element of optsret is set to 0 (false) # or 1 (true). For non-boolean options, the corresponding element of # optsret contains whatever value was specified. For non-boolean options # which are not specified, the corresponding element of optsret is not # created and/or modified. proc getopts2 { optstr argvret optsret } { upvar $argvret argv upvar $optsret opts # initialize opts array with all boolean options set to 0 (false) set opts(error) [ set opts(opt) "" ] set opts_list [ split $optstr "" ] for { set idx 0 } { $idx < [ llength $opts_list ] } { incr idx } { if {[ lindex $opts_list [ expr $idx+1 ]] == ":" } { incr idx } else { set opts([ lindex $opts_list $idx ]) 0 } } while {[ set rc [ getopt $optstr $argv state opt_ch optarg ]] > 0 } { set opts($opt_ch) $optarg } if { $rc < 0 } { set opts(error) $optarg set opts(opt) $opt_ch return -1 } set argv [ lrange $argv $state(idx) end ] return 0 } #****************************************************************************** # define $prog and $usagemsg set prog $::argv0 regsub {.*/} $prog "" prog set usagemsg " usage: $prog \[-ab] \[-o VAL] ARG... -a option a -b option b -o VAL option o requires a value " regsub -all {\A\n|\n\Z} $usagemsg "" usagemsg ;# remove leading/trailing \n #****************************************************************************** # usage message procedure, using $prog and $usagemsg proc usage {{ msg "" }} { global prog usagemsg if { $msg != "" } { puts stderr "$prog: $msg" } puts stderr $usagemsg exit 1 } #****************************************************************************** # run against actual script arguments with hardcoded dummy test options if {[ getopts "?abo:" argv opts ] || $opts(?) } { usage $opts(error) } #****************************************************************************** # run tests proc array2list { arrname } { upvar $arrname arr foreach name [ lsort [ array names arr ]] { lappend out $name $arr($name) } return $out } proc compare_versions { tag optstr argv_orig } { global fail set argv $argv_orig array unset opts set rc [ getopts $optstr argv opts ] set out1 "rc:$rc opts:[ array2list opts ] args:$argv" set argv $argv_orig array unset opts set rc [ getopts2 $optstr argv opts ] set out2 "rc:$rc opts:[ array2list opts ] args:$argv" if { $out1 == $out2 } { return } set fail 1 puts "$tag mismatch orig argv:$argv_orig" puts " out1=<$out1>" puts " out2=<$out2>" } proc test_versions {} { set cnt1 0 foreach optstr [ list \ "" \ "?" \ ":" \ "?a" \ "?ao:" \ "?o:a" \ ] { incr cnt1 set cnt2 0 foreach argv [ list \ [ list ] \ [ list "?" ] \ [ list "-a-" ] \ [ list "-o" ] \ [ list "-o" "" ] \ [ list "-ao" ] \ [ list "-ao" "" ] \ [ list "-ao" "x" ] \ [ list "-a" "-ox" ] \ ] { incr cnt2 compare_versions "$cnt1/$cnt2" $optstr $argv } } } proc check_results {} { global fail set cnt1 0 foreach { optstr argv expected } [ list \ "" [ list ] \ "rc:0 opts:error {} opt {} args:" \ "" [ list fred ] \ "rc:0 opts:error {} opt {} args:fred" \ "" [ list - fred ] \ "rc:0 opts:error {} opt {} args:- fred" \ "" [ list : fred ] \ "rc:0 opts:error {} opt {} args:: fred" \ "" [ list -- fred ] \ "rc:0 opts:error {} opt {} args:fred" \ "?o:a" [ list -a- ] \ "rc:-1 opts:? 0 a 1 error {invalid option -} opt - args:-a-" \ "?o:a" [ list -ao fred ] \ "rc:0 opts:? 0 a 1 error {} o fred opt {} args:" \ "?o:a" [ list -a fred ] \ "rc:0 opts:? 0 a 1 error {} opt {} args:fred" \ "?o:a" [ list -a -o fred bob ] \ "rc:0 opts:? 0 a 1 error {} o fred opt {} args:bob" \ "?o:a" [ list -x fred ] \ "rc:-1 opts:? 0 a 0 error {invalid option x} opt x args:-x fred" \ "o:" [ list -o ] \ "rc:-1 opts:error {missing argument for option o} opt o args:-o" \ ] { array unset opts set rc [ getopts $optstr argv opts ] set actual "rc:$rc opts:[ array2list opts ] args:$argv" if { $actual == $expected } { continue } set fail 1 puts "Fail" puts "actual: $actual" puts "expected: $expected" } } set fail 0 test_versions check_results if { $fail } { puts stderr "Failed one or more tests" exit 1 } puts stderr "Success" exit 0 #******************************************************************************
RFox - 2013-05-27 10:18:35http://tcllib.sourceforge.net/doc/cmdline.html
jrw32982 Here's the source for tcllib which includes cmdline.tcl: http://sourceforge.net/projects/tcllib/files/tcllib/ . However, cmdline.tcl is nothing like getopts, which is why I had to write getopts in the first place.