I want to create tcl procedures with some options. I know procedures with arguments and optional arguments, but don't know options. For example, if I am calling my procedure arith in following three ways (-add for addition, -sub for subtraction):
1) arith 10 5
2) arith -add 10 5 or arith 10 5 -add
3) arith -sub 10 5 or arith 10 5 -sub
Respectively output should be 1) 15 (by default it should add), 2) 15, 3) 5
How to write this procedure in Tcl? I am new to tcl, please suggest me some online material or book for Tcl.
Complex argument parsing can be done with the cmdline package, which is part of Tcllib. The key command is ::cmdline::getoptions, which extracts the options from a variable and returns a dictionary describing them. It also modifies the variable so it contains just the arguments left over.
package require cmdline
proc arith args {
set options {
{op.arg "add" "operation to apply (defaults to 'add')"}
}
array set parsed [::cmdline::getoptions args $options]
if {[llength $args] != 2} {
return -code error "wrong # args: must be \"arith ?-op operation? x y\""
}
switch $parsed(op) {
add {return [::tcl::mathop::+ {*}$args]}
sub {return [::tcl::mathop::- {*}$args]}
default {
return -code error "Unknown -op \"$parsed(op)\": must be add or sub"
}
}
}
Demonstrating usage (including some error cases):
% arith
wrong # args: must be "arith ?-op operation? x y"
% arith 2 3
5
% arith -op sub 2 3
-1
% arith -op mult 2 3
Unknown -op "mult": must be add or sub
The main thing to be aware of is that the options descriptor takes the names of options without a leading - and with .arg on the end if you want to have an argument passed as well.
When it comes to options, it's a good idea to use even number of arguments
-op add -values {10 5}
-op sub -values {10 5}
With this, you can put the arguments into array as,
array set aArgs $args
where args is nothing but arguments passed to procedure.
proc arith {args} {
if {[catch {array set aArgs $args} err]} {
puts "Error : $err"
return 0
}
if {![info exists aArgs(-op)] || ![info exists aArgs(-values)] || [llength $aArgs(-values)]!=2} {
puts "Please pass valid args"
return 0
}
set x [lindex $aArgs(-values) 0]
set y [lindex $aArgs(-values) 1]
switch $aArgs(-op) {
"add" {
puts [expr {$x+$y}]
}
"sub" {
puts [expr {$x-$y}]
}
}
}
arith -op add -values {10 5}
In this example... I'm assuming that the operator is either the first or last option. The value would be -add or add. Just make the changes.
There is an optimization in the computation... to use lrange and ::mathop::.
proc arith args {
if {[llength $args] != 3} {
return -code error "wrong # args: must be \"arith ?-op operation? x y\""
}
set isadd [lsearch $args add]
set issub [lsearch $args sub]
if {$isadd != -1 && $issub == -1} {
return [expr [lindex $args 1] + [lindex $args [expr $isadd == 0 ? 2: 0]]]
}
if {$issub != -1 && $isadd == -1} {
return [expr [lindex $args [expr $issub == 0 ? 1: 0]] - [lindex $args [expr $issub == 0 ? 2: 1]]]
}
return -code error "Unknown -op must be add or sub"
}
example:
#arith add 1 2 3
#arith add sub 2
puts [arith add 1 2]
puts [arith 1 2 add]
puts [arith sub 1 2]
puts [arith 1 2 sub]
The two error examples were commented out because I need a catch instead... but it really depends on the big picture and how reusable it's intended to be.
proc arith args {
if {[llength $args] != 3} {
return -code error "wrong # args: must be \"arith ?-op operation? x y\""
}
set isadd [lsearch $args -add]
set issub [lsearch $args -sub]
if {$isadd != -1 && $issub == -1} {
return [::tcl::mathop::+ {*}[lrange $args [expr $isadd == 0] [expr ($isadd == 0) + 1]]]
}
if {$issub != -1 && $isadd == -1} {
return [::tcl::mathop::- {*}[lrange $args [expr $issub == 0] [expr ($issub == 0) + 1]]]
}
return -code error "Unknown -op must be add or sub"
}
Related
I have the following procedure:
proc test {a {b 10} {c 30}} {
puts "$a $b $c"
}
I would like to call the test procedure by passing value to argument a and c and keep the default value of the argument b. In other words, I want to pass arguments value by name.
Is it possible to do it in TCL?
No.
The usual method to do this is:
proc test { args } {
# set up the defaults
set a(-a) {}
set a(-b) 10
set a(-c) 30
set len [llength $args]
# some basic argument checking
if { $len > 6 || $len < 2 || $len % 2 != 0 } {
error "Invalid arguments"
}
array set a $args
...
}
set result [test -a 3 -c 40]
I am trying to write a tcl procedure which does the following -
proc myProc {arg1 def1} {arg2 def2} {
...
...
}
tcl> myProc -arg1 val1 -arg2 val2
arg1 variable has val1
arg2 variable has val2
tcl> myProc -arg1 val1
arg1 variable has val1
arg2 variable has def2
tcl> myProc -?
myProc -arg1 <value1> -arg2 <value2>
arg1 - required argument [default value is 10]
arg2 - optional argument [default value is 20]
help - print this message
? - print this message
Is this possible in tcl?
I looked up some of the questions that have been asked and what I see is this question. This does partially what I require but I couldn't find anything that would help me solve my problem. Please guide me!
Read the proc man page carefully: the list of arguments has to be a single list. You were thinking about this:
% proc myproc {{a 42} {b 54}} { puts "a=$a b=$b" }
% myproc
a=42 b=54
% myproc 1
a=1 b=54
% myproc 1 2
a=1 b=2
Note that the first argument is assigned to a -- you cannot provide a value for b and use the default value for a with this method.
To use command-line-like options, the simplest way is this:
% proc myproc {args} {
array set data [list -a 42 -b 54 {*}$args]
puts "a=$data(-a) b=$data(-b)"
}
% myproc
a=42 b=54
% myproc -a 1
a=1 b=54
% myproc -b 2
a=42 b=2
% myproc -b 2 -a 3
a=3 b=2
% myproc -c 4
a=42 b=54
One problem with this method is that you must pass an even number of arguments, or array set will throw an error:
% myproc 12
list must have an even number of elements
You can use Tcllib module cmdline. On wiki you can find example how to use this module with proc.
proc printdata args {
array set param [::cmdline::getoptions args {\
{page.arg 1 "current page"}
{pages.arg 1 "number of pages"}
} "printdata ?options? Data"]
if {1 != [llength $args]} {
return -code error "No data given"
}
set param(data) [lindex $args 0]
# processing here
parray param
}
% printdata -pages 2 -- "--Head data to print--"
param(data) = --Head data to print--
param(page) = 1
param(pages) = 2
% printdata -?
printdata ?options? Data
-page value current page <1>
-pages value number of pages <1>
-help Print this message
-? Print this message
I wrote a utility function for this years ago. The arguments have the same syntax as proc but it generates procs that work the way you describe. The code is simple:
proc optproc {name args script} {
proc $name args [
string map [list ARGS $args SCRIPT $script] {
foreach var {ARGS} {
set [lindex $var 0] [lindex $var 1]
}
foreach {var val} $args {
set [string trim $var -] $val
}
SCRIPT
}
]
}
What the code does above is basically call proc with two foreach loops injected to the function body to process the arguments.
With this you can declare your function like this:
optproc myProc {{arg1 def1} {arg2 def2}} {
# you can use arg1 and arg2 just like a regular proc:
puts $arg1
puts $arg2
}
Which you can then use the way you wanted:
myProc ;# default values for arg1 and arg2
myProc -arg1 foo ;# default values for arg2
myProc -arg2 bar ;# default values for arg1
myProc -arg1 foo -arg2 bar
For more info and discussion on this you can read the wiki page: http://wiki.tcl.tk/20066
For even more flexible proc argument processing you can use a while loop: http://wiki.tcl.tk/16032
Thank you all for your valuable inputs. I have written the following code which helps me achieve what I want -
Code
proc argsParser args {
set args [string map { - "" } $args]
if {[expr [llength $args] % 2] != 0 } {
puts "Wrong Arguments :: $args "
return "For Help :: argsParser -?";
}
switch $args {
h -
help -
? { puts "Usage :: argParser -arg1 val1 -arg2 val2"
puts "arg1 :: Number"
puts "arg2 :: Number"
}
default {
set arg1 10 ;
set arg2 20 ;
set args [string map { - "" } $args]
for {set i 0} {$i < [llength $args]} { incr i 2} {
set a [lindex $args $i]
if { $a != "arg1" && $a != "arg2" } {
return "Unknown Args :: $a - For Help :: argsParser -?";
}
set b [lindex $args [expr $i+1]]
set $a $b
}
puts "arg1 - $arg1"
puts "arg2 - $arg2"
}
}
}
Result
tcl> argsParser -arg1 20 -arg4 40
Unknown Args :: arg4 - For Help :: argsParser -?
tcl> argsParser -arg1 20 -arg2 40
arg1 - 20
arg2 - 40
Im searching all over the internet , i guess im searching not the right keywords
i tried most of them :)
i want to create in tcl/bash a proc with hyphen flags to get arguments with flags from the user
ex.
proc_name -color red -somethingselse black
It's very easy to do, actually. This code allows abbreviated option names, flag options (-quxwoo in the example) and the ability to stop reading options either with a -- token or with a non-option argument appearing. In the example, unknown option names raise errors. After passing the option-parsing loop, args contains the remaining command-line arguments (not including the -- token if it was used).
proc foo args {
array set options {-bargle {} -bazout vampires -quxwoo 0}
while {[llength $args]} {
switch -glob -- [lindex $args 0] {
-bar* {set args [lassign $args - options(-bargle)]}
-baz* {set args [lassign $args - options(-bazout)]}
-qux* {set options(-quxwoo) 1 ; set args [lrange $args 1 end]}
-- {set args [lrange $args 1 end] ; break}
-* {error "unknown option [lindex $args 0]"}
default break
}
}
puts "options: [array get options]"
puts "other args: $args"
}
foo -barg 94 -quxwoo -- abc def
# => options: -quxwoo 1 -bazout vampires -bargle 94
# => other args: abc def
This is how it works. First set default values for the options:
array set options {-bargle {} -bazout vampires -quxwoo 0}
Then enter a loop that processes the arguments, if there are any (left).
while {[llength $args]} {
During each iteration, look at the first element in the argument list:
switch -glob -- [lindex $args 0] {
String-match ("glob") matching is used to make it possible to have abbreviated option names.
If a value option is found, use lassign to copy the value to the corresponding member of the options array and to remove the first two elements in the argument list.
-bar* {set args [lassign $args - options(-bargle)]}
If a flag option is found, set the corresponding member of the options array to 1 and remove the first element in the argument list.
-qux* {set options(-quxwoo) 1 ; set args [lrange $args 1 end]}
If the special -- token is found, remove it from the argument list and exit the option-processing loop.
-- {set args [lrange $args 1 end] ; break}
If an option name is found that hasn't already been dealt with, raise an error.
-* {error "unknown option [lindex $args 0]"}
If the first argument doesn't match any of the above, we seem to have run out of option arguments: just exit the loop.
default break
Documentation: array, break, error, lassign, lindex, llength, proc, puts, set, switch, while
With array set, we can assign the parameters and their values into an array.
proc getInfo {args} {
# Assigning key-value pair into array
# If odd number of arguments passed, then it should throw error
if {[catch {array set aInfo $args} msg]} {
return $msg
}
parray aInfo; # Just printing for your info
}
puts [getInfo -name Dinesh -age 25 -id 974155]
will produce the following output
aInfo(-age) = 25
aInfo(-id) = 974155
aInfo(-name) = Dinesh
The usual way to handle this in Tcl is by slurping the values into an array or dictionary and then picking them out of that. It doesn't offer the greatest amount of error checking, but it's so easy to get working.
proc myExample args {
# Set the defaults
array set options {-foo 0 -bar "xyz"}
# Read in the arguments
array set options $args
# Use them
puts "the foo option is $options(-foo) and the bar option is $options(-bar)"
}
myExample -bar abc -foo [expr {1+2+3}]
# the foo option is 6 and the bar option is abc
Doing error checking takes more effort. Here's a simple version
proc myExample args {
array set options {-foo 0 -bar "xyz"}
if {[llength $args] & 1} {
return -code error "must have even number of arguments in opt/val pairs"
}
foreach {opt val} $args {
if {![info exist options($opt)]} {
return -code error "unknown option \"$opt\""
}
set options($opt) $val
}
# As before...
puts "the foo option is $options(-foo) and the bar option is $options(-bar)"
}
myExample -bar abc -foo [expr {1+2+3}]
# the foo option is 6 and the bar option is abc
# And here are the errors it spits out...
myExample -spregr sgkjfd
# unknown option "-spregr"
myExample -foo
# must have even number of arguments in opt/val pairs
#flag defaults
set level 1
set inst ""
# Parse Flags
while {[llength $args]} {
set flag [lindex $args 0]
#puts "flag: ($flag)"
switch -glob $flag {
-level {
set level [lindex $args 1]
set args [lrange $args 2 end]
puts "level:($level) args($args)"
} -inst {
set autoname 0
set inst [lindex $args 1]
set args [lrange $args 2 end]
puts "inst:($inst) args($args)"
} -h* {
#help
puts "USAGE:"
exit 1
} -* {
# unknown option
error "unknown option [lindex $args 0]"
} default break
}
}
# remaining arguments
set filename "$args"
puts "filename: $args"
I found a wiki page about how to calculate the depth of a list:
http://wiki.tcl.tk/11602
How can I rewrite the above code as a single proc using tcl 8.6 features lmap and apply? Perhaps "apply" is not really needed.
proc max list {
set res [lindex $list 0]
foreach e [lrange $list 1 end] {if {$e>$res} {set res $e}}
set res
}
# llmap perhaps can be replaced with lmap from Tcl 8.6
proc llmap {func list} {
set res {}
foreach e $list {lappend res [$func $e]}
set res
}
proc ldepth list {
expr {
[llength $list] == 0? 1:
[expr {[lindex $list 0] eq $list}]? 0:
1+[max [llmap ldepth $list]]
}
}
The first level of adaptation already gets us close to where you want to go, sufficiently so that this is what I'd consider as a production solution:
proc ldepth {list} {
expr {
[llength $list] == 0 ? 1 :
[lindex $list 0] eq $list ? 0 :
1 + [tcl::mathfunc::max {*}[lmap e $list {
ldepth $e
}]]
}
}
This uses the standard lmap and tcl::mathfunc::max (which is the implementation of the max() function). Note that expansion and tcl::mathfunc::max are features of Tcl 8.5, but they're very useful here.
Eliminating expansion
Let's see if we can get rid of that call to tcl::mathfunc::max with the expansion.
proc ldepth {list} {
set m -inf
expr {
[llength $list] == 0 ? 1 :
[lindex $list 0] eq $list ? 0 :
1 + [lindex [lmap e $list {
set m [expr { max($m, [ldepth $e]) }]
}] end]
}
}
Hmm, that's just a touch ugly. We might as well do this:
proc ldepth {list} {
set m -inf
expr {
[llength $list] == 0 ? 1 :
[lindex $list 0] eq $list ? 0 :
[foreach e $list {
set m [expr { max($m,[ldepth $e]) }]
}
expr {$m + 1}]
}
}
This definitely isn't getting better, except in that it doesn't keep so much state around (just a running maximum, not a list of depths). Let's go back to the version with lmap!
(What is really needed for true beauty is lfold, but that didn't get done on the grounds that sometimes you've just got to stop adding features and call a release.)
“Eliminating” recursion
The other way we can go is to see about removing the outer recursion. We can't completely eliminate the recursion altogether — we're dealing with a recursive operation over a recursive structure — but we don't need to put it in the outer level where a rename ldepth fred will cause problems. We do this by using apply to create an internal procedure-like thing, and since we're doing recursive calls, we pass the lambda term into itself. (There are tricks you can do to get that value without explicitly passing it in, but they're ugly and we might as well be honest here.)
proc ldepth {list} {
set ldepth {{ldepth list} {expr {
[llength $list] == 0 ? 1 :
[lindex $list 0] eq $list ? 0 :
1 + [tcl::mathfunc::max {*}[lmap e $list {
apply $ldepth $ldepth $e
}]]
}}
apply $ldepth $ldepth $list
}
Full-bytecode version
Subject to still doing a recursive call.
proc ldepth {list} {
expr {
[llength $list] == 0 ? [return 1] :
[lindex $list 0] eq $list ? [return 0] :
[set m -inf
foreach e $list {
set m [expr {[set d [ldepth $e]]+1>$m ? $d+1 : $m}]
}
return $m]
}
}
Fully recursion-free by using a work queue instead. This is 8.5 code — no 8.6 features required — and you could write this to be 8.4-suitable by replacing the lassigns:
proc ldepth {list} {
set work [list $list 0]
set maxdepth 0
while {[llength $work]} {
### 8.4 version
# foreach {list depth} $work break
# set work [lrange $work 2 end]
set work [lassign $work[unset -nocomplain work] list depth]
if {[llength $list] == 0} {
incr depth
} elseif {[lindex $list 0] ne $list} {
incr depth
foreach e $list {
lappend work $e $depth
}
continue
}
set maxdepth [expr {$maxdepth<$depth ? $depth : $maxdepth}]
}
return $maxdepth
}
The moral of the story? The 8.6 features don't make sense for everything.
Here's a simple one that works.
It just flattens the list until it can't be flattened any further. The number of attempts is the depth. No recursion needed.
proc ldepth {lst} {
set depth 1
set fatter $lst
set flatter [join $fatter]
while {$flatter ne $fatter} {
set fatter $flatter
set flatter [join $fatter]
incr depth
}
return depth
}
Hope this helps!
I have a list like
set val [ list Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 ]
now i want to put it in a loop and execute some commands over each range
like
set number 0
set pattern 0
foreach n $val {
if {$pattern == 0} {
set current $n
regexp {(.*/)(\d+)} $n - pattern number
continue
}
regexp {(.*/)(\d+)} $n - match1 match2
if {$match1 == $pattern} {
#puts "someproc $current - match2"
}
}
I am unable to get this work the output should be like for ech pair or singular value found
someproc Fa2/0/1 - 24
someproc Gi1/0/13 - 23
someproc Gi1/1/1 - 1 #for singular values
someproc Gi2/0/1 - 1
EDIT : i have a list of such data like :
Gi3/0/1 Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 Te1/0/1
where you can say each data can be of type Gi3/0/ or Gi2/0/ or Fa2/0/ these reperesent some range of ports on cisco swicth.Now for every type i need to execute some command for a range.Again taking the above list i can get.
somecommand Gi3/0/1 - 1 # there is only one `Gi3/0/` with number 1.
somecommand Fa2/0/1 - 24 # range of `Fa2/0/` is 1 to 24
similarly,
somecommand Gi1/0/13 - 23
somecommand Gi1/1/1 - 1
and so on
#!/usr/bin/tcl
## Assumptions:
## The pattern will always be X/X/X
## The values are given in list
set val_list [list Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1]
array set pattern {}
foreach item $val_list {
set parent [file dir $item]
set val [file tail $item]
if {[info exists pattern($parent,L)] && [info exists pattern($parent,H)] } {
if {$pattern($parent,L) > $val } {
set pattern($parent,L) $val
} elseif { $pattern($parent,H) < $val} {
set pattern($parent,H) $val
}
} else {
set pattern($parent,L) $val
set pattern($parent,H) $val
}
}
array set count {}
foreach pat [array names pattern] {
set pat [lindex [split $pat ,] 0]
if {![info exists count($pat)] } {
puts "$pat $pattern($pat,L) - $pattern($pat,H)"
set count($pat) 1
}
}
/*The output will be
Gi1/0 13 - 23
Fa2/0 1 - 24
Gi2/0 1 - 1
Gi1/1 1 - 1
*/
Hope this is what you are requesting for. I used array "count" to remove duplicate entries in output, which needs to be avoided. Hope if someone can suggest any better way. And FYI I am using 8.4 version of TCL.
If you are not sure how arrays, work, you can edit the code you posted as an answer to this code:
set number 0
set pattern 0
set current 0
set result [list Gi3/0/1 Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 Te1/0/1]
foreach n [lsort $result] {
if {$pattern == 0} {
set current $n
regexp {(.*/)(\d+)} $n - pattern number
continue
}
regexp {(.*/)(\d+)} $n - match1 match2
if {$match1 == $pattern} {
set number $match2
} else {
puts "$current - $number"
set pattern $match1
set number $match2
set current $n
}
}
That works for me :)
The output (note that I sorted the list first so you only have to worry about the increasing $number or $match2 while not having to bother too much about the $pattern):
Fa2/0/1 - 24
Gi1/0/13 - 23
Gi1/1/1 - 1
Gi2/0/1 - 1
Gi3/0/1 - 1
Here is my solution, which does not use array (nothing is wrong with array, my solution just don't need it), and it does it in one pass (i.e. only one loop).
set val [ list Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 ]
set lastPattern ""
set lastNumber 0
lappend val x/1/1; # Add a trailer to ease processing
foreach item $val {
# If item=Fa2/0/1, then pattern=Fa2/0 and number=1
set pattern [file dirname $item]
set number [file tail $item]
if {$pattern == $lastPattern} {
# We have seen this pattern before
puts "$pattern/$lastNumber - $number"
set lastPattern ""
} else {
# This is a new pattern, print the old one if applicable then
# save the pattern and number for later processing
if {$lastPattern != ""} {
puts "$lastPattern/$lastNumber - $lastNumber"
}
set lastPattern $pattern
set lastNumber $number
}
}
set val [lrange $val end-1]; # Remove the trailer
If you want to compare adjacent list elements, it might be cleaner to use a C-style for loop:
for {set i 0} {$i < [llength $val] - 1} {incr i} {
set current [lindex $val $i]
set next [lindex $val [expr {$i+1}]]
# ...
}
Or, a bit more esoteric
set l {a b c d e f g}
foreach current [lrange $l 0 end-1] \
next [lrange $l 1 end] {
puts "$current $next"
}
outputs
a b
b c
c d
d e
e f
f g
You could even write a new control structure, similar to Ruby's each_cons
proc foreach_cons {vars list body} {
foreach varname $vars {upvar 1 $varname $varname}
set numvars [llength $vars]
for {set i 0} {$i <= [llength $list]-$numvars} {incr i} {
lassign [lrange $list $i [expr {$i + $numvars}]] {*}$vars
uplevel 1 $body
}
}
foreach_cons {a b c} $l {puts "$a $b $c"}
a b c
b c d
c d e
d e f
e f g
Why don't you loop over pairs of the list?
foreach {v1 v2} $val {
someproc $v1 $v2
}
You might check if both values are similar, extract the parts that you need etc.
I came up with a awkward solution of my own :
where reslut is the list :
Gi3/0/1 Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 Te1/0/1
#
set number 0
set pattern 0
set last_element [lindex $result end]
set first_element [lindex $result 0]
foreach n $result {
if {$pattern == 0} {
set current $n
set count 0
regexp {(.*/)(\d+)} $n - pattern number
continue
}
regexp {(.*/)(\d+)} $n - match1 match2
if {$match1 == $pattern} {
set count 0
puts " $current - $match2"
continue
} else {
if {"$last_element" == "$n"} {
puts "$last_element"
}
if {"$first_element" == "$current"} {
puts "$first_element"
}
incr count
if {"$count" == 1} {
set pattern $match1
set current $n
continue
} else {
if {$match1 != $pattern} {
puts "$current"
}
}
set pattern $match1
}
set current $n
}
This solution is a little shorter, but requires Tcl 8.5.
First, create a dictionary structure with the first two fields as key and subkey, and collect lists of values from the third field as dictionary values:
set data {}
foreach v $val {
lassign [split $v /] a b c
if {![dict exists $data $a $b]} {
dict set data $a $b {}
}
dict with data $a {
lappend $b $c
set b [lsort –integer $b]
}
}
Then iterate over this dictionary structure, calling the someproc command for each combination of key, subkey, first and last value.
dict for {a v} $data {
dict for {b v} $v {
someproc $a/$b/[lindex $v 0] - [lindex $v end]
}
}
Documentation: dict, foreach, if, lappend, lassign, lindex, set, split