command options are ever-present in Tcl scripts. This page presents various approaches to processing them.
suggests several distinct topics:
Certain considerations are common to all these, and are convenient to treat here in a unified way.
One simple approach to processing inputs is to treat $args as a dictionary, unpacking the contents into local variables. This approach turns argument parsing into a one-liner.
proc dostuff {p1 p2 args} { dict with $args {} do stuff with $p1 $p2 and $some $other $values }
As RS points out, this approach is given in Practical Programming in Tcl and Tk:
proc foo args { # first assign default values... array set options {-bar 1 -grill 2 -verbose 0 ...} # ...then possibly override them with user choices array set options $args .... }
Sometimes it makes sense to use $args to collect a sequence array of values, in which case the options can be one of the positional parameters, but at a cost of slightly more inconvenience to the caller:
proc dostuff {options p1 p2 args} { [dict with] $options {} do stuff with $p1 $p2 and $some $other $values }
To restrict what is unpacked into a variable, use dict update:
proc dostuff {p1 p2 args} { dict update $args name name score score {} do stuff with $name and $score }
There's often no need to explicitly check that a mandatory argument has been provided in the options, as the interpreter return an error when the script attempts to access an undefined variable. If an explicit early check turns out to be needed, an identity command can be put to good effect:
proc dostuff {p1 p2 args} { dict update $args name name score score {} #check that $score was provided lindex $score ... do stuff with $score }
Question: In C, for Tcl, using the newer objc, objv API, how do I parse options? I would have thought people would need to do this all the time, but I can't find anything. Needs to handle args, options, required optional etc. Speed is high on the priority list...
If you can use the Tk library, try Tk_SetOptions and its assorted support functions. It would be nice if something like this were in the Tcl library, for those times when one doesn't have Tk around. A pure-Tcl solution might be to do the option handling in Tcl, passing a fixed number of arguments to the C code, but that might not meet your speed requirements.
SPB: I've modified the Tk_ParseArgv() routine in tkArg.[hc] to handle this case. Also, to make it even more useful, I added a Tcl_ParseArgsObjv for parsing a bunch of arguments passed to a C-implemented Tcl command. For example, you can do something like:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <tcl.h> #include <tclArgv.h> int mycoolcommand(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { int mytoggle=0; int myint=-2; Tcl_ArgvInfo argTable[] = { {"-toggle",TCL_ARGV_CONSTANT,(void*)1,&mytoggle,"turn toggle on"}, {"-theint",TCL_ARGV_INT,NULL,&myint,"set myint"}, {(char*)NULL,TCL_ARGV_END,NULL,NULL,(char*)NULL} }; Tcl_Obj **private_objv=(Tcl_Obj**)calloc(objc+1,sizeof(Tcl_Obj*)); int i; for (i=0;i<objc;i++) private_objv[i]=objv[i]; if (Tcl_ParseArgsObjv(interp,&objc,private_objv,argTable,0)!=TCL_OK) return TCL_ERROR; /* after this call, private_objv only holds unprocessed arguments, and objc reflects this. */ ... do stuff... return TCL_OK; }
If you feel this may be of use, you can grab the tar file from http://www.sambromley.com/tclArgv/
Sam.
Here is the start of some code to show at least one method of doing command line parsing. Hopefully people will contribute other samples as appropriate.
# If this script was executed, and not just "source"'d, handle argv if {[string compare [info script] $argv0] == 0} { while {[llength $argv] > 0 } { set flag [lindex $argv 0] switch $flag { -bool { set bool 1 set argv [lrange $argv 1 end] } -option { set value [lindex $argv 1] set argv [lrange $argv 2 end] } default break } } } foreach file $argv { puts "[format "file: %s" $file]" }
PT writes: I like to use code similar to the above example for option processing. However, we want to avoid 'shimmering' the args list into a string and back into a list. Plus I find it repetative to keep writing the same two lines for setting the option value. Here is my current example:
# ------------------------------------------------------------------------- # Description: # Pop the nth element off a list. Used in options processing. # proc dns::Pop {varname {nth 0}} { upvar $varname args set r [lindex $args $nth] set args [lreplace $args $nth $nth] return $r } # --------------------------------------------------------------------- # Now the option processing loop. Eats the arguments once handled and stops at '--' # setup the defaults. array set opts {-a 1 -b 0} while {[string match -* [lindex $args 0]]} { switch -glob [lindex $args 0] { -A* -a* { set opts(-a) [Pop args 1] } -b* { set opts(-b) [Pop args 1] } -- { Pop args ; break } default { set opts [join [lsort [array names state -*]] ", "] return -code error "bad option [lindex $args 0]: \ must be one of $opts" } } Pop args } puts "Options now [array get opts] and the remaining args: $args"
A quick and dirty way uses one-liners like this:
if {[regexp { -x} $::argv]} {# do the X thing} ;# RS
If your values come in pairs (like -option value), I usually use the following code for parsing them:
foreach {option value} $argument_list { switch -glob $option { -opt* {set opt $value} -otheropt* {set otheropt $value} default {error "Unknown option $option!"} } }
troym72 2009-12-22: Passing arguments from the UNIX command line in the form of a keyed list, can be a little tricky. Here's an example procedure that I created to help illustrate how Tcl handles arguments from the command line in keyed list form.
#!/usr/bin/tcl # Script Name: argstest.tcl # Date Created: 12/22/2009 # Author: Troy Morton # Purpose: Test command line arguments package require tclx proc parseargs args { echo $args ;# args as passed from the “parseargs $argv procedure call” set args [lindex $args 0] ;# pull my args keyed list from the argv single element list keylget args KEY1 value ;# now do my keylget from the args which is now a properly formatted keyed list. echo $value ;# echo value of the key “KEY1”. } echo $argv ;# args from command line arrive as keyed list parseargs $argv ;# argv is passed to pareargs as a list
When run from the UNIX command line the above script looks like this:
/>argstest.tcl "KEY ValueKey1" "KEY2 ValueKey2" ;# typed on command line {KEY1 ValueKey1} {KEY2 ValueKey2} ;# argv as received by the tcl script {{KEY1 ValueKey1} {KEY2 ValueKey2}} ;# argv as received by proc parseargs ValueKey1 ;# value of the key KEY1
PYK 2015-10-20: That approach bundles a key and a value into a single argument, which can lead to quoting issues between the system shell and the Tcl interpreter. Better to treat $argv as a sequence of key-value pairs:
proc main {$argv0 $argv} { dict update $argv KEY KEY KEY2 KEY2 {} ... } main $argv0 $argv
SB 2003-05-21: For many of my tcl utilities I have only vanilla tclsh but still need to parse command line options. As I mostly use the tools myself I don't need too much fancy error detection. For a small application, I need a couple of switches, one with an argument and then the file to process. Nothing fancy, but it is written in a matter of seconds, and it is fairly easy to extend with more switches.
set arglen [llength $argv] set index 0 while {$index < $arglen} { set arg [lindex $argv $index] switch -exact $arg { -s { set args($arg) [lindex $argv [incr index]] } -l { set args($arg) . } default { set filename [lindex $argv $index] } } incr index } if {[info exists args(-l)]} { puts "-l switch set" ... code what happens for -l } if {[info exists args(-s)]} { puts "-s switch set with arg $args(-s)" ... code what happens for -s and its arg }