Subsitution of part of dictionary commands - tcl

I'm having trouble understanding how to substitute a list of values in dictionary commands.
Would you please tell me what I'm doing wrong/not doing?
Thank you.
set comPorts {
001 {
socket 123
user abc
type http
}
002 {
socket 789
user abc
type http
}
}
# This statement works directly.
#dict set comPorts 002 type port
# These all error.
set path "002 type port"
set path { 002 type port }
set path [list 002 type port]
dict set comPorts $path
chan puts stdout $comPorts

Try
dict set comPorts {*}$path
See documentation at https://www.tcl-lang.org/man/tcl8.6/TclCmd/Tcl.htm#M9

Related

How to control the character length of strings in tcl errors?

Long strings which appear in tcl error messages are elided with ... after 150 characters:
proc helloWorld {a} {
throw
}
helloWorld "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
Error:
invalid command name "throw"
while executing "throw "
(procedure "helloWorld" line 2)
invoked from within "helloWorld "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff..."
(file "mytcl" line 6)
Is there a way to change this so that more characters are shown?
In my large commercial tcl application, customers use file paths that are more than 150 characters long. We see 100 characters for a repository name, plus 100 characters for a location within the repository. Since only 150 characters appear, this means the "useful" part of the filename is not displayed in error messages. We have suggested to the customer that they could shorten the path using a symlink, but they do not accept this.
You can install your own background error handler.
You can log the error, display it how you want, etc.
interp bgerror {} ::bgerrhandler
# give err and res whatever names that make sense to you
proc ::bgerrhandler { err res } {
# do stuff
return
}
The problem that you've got is that the line-shortening is applied during the construction of the error trace message. It's already long gone by the time you get to see it. What you need instead is to install a custom background error handler using the new API that picks critical things out of the error stack. (The old API using a simple bgerror command is unsuitable for this task as it hides the result option dictionary for backward compatibility reasons.)
proc MyErrorHandler {msg opts} {
if {[dict get $opts -code] == 1} {
# Actually an error!
puts "ERROR: $msg"
foreach {op details} [dict get $opts -errorstack] {
puts stderr "$op :> $details"
}
return
}
# Transfer to standard implementation; tailcall recommended for this from 8.6 on
::tcl::Bgerror $msg $opts
}
interp bgerror {} MyErrorHandler
Here's an example of this working:
# Error generation code
proc foo {x} {throw BOOM $x}
proc bar {y} {foo $y$y$y$y}
proc grill {} {bar skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd}
# Now actually do things in the background
after idle grill; after 50 set z 1; vwait z
That will print this message:
ERROR: skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd
INNER :> returnImm skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd {-errorcode BOOM}
CALL :> foo skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd
CALL :> bar skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd
CALL :> grill
The INNER indicates what bytecode opcode actually threw, and then there's a sequence of CALLs to give the exact arguments lists that lead to that point.

How can I search through an Expect variable

I'm working on an expect script that connects to a switch and then shows the configuration for an interface. I then analyze this output to check for certain things. I would like to store the output of one of the things that I'm checking, which I'm trying to accomplish by searching through $expect_out(buffer), although I'm having a difficult time finding out how to do this.
How should I go about doing this?
The script looks like the following (cut out unnecessary stuff):
send "show running-config interface $intf\r"
log_user 0
expect "#"
if {[string match "*service-policy input Access-Port*" $expect_out(buffer)]} {
set servicepolicy "yes"
} else {
set servicepolicy "no"
}
if {[string match "*mls qos trust dscp*" $expect_out(buffer)]} {
set mlsqos "yes"
} else {
set mlsqos "no"
}
if {[string matc "*Description*" $expect_out(buffer)]} {
EXTRACT DESCRIPTION STRING FROM $expect_out(buffer)
}
This is what the output of $expect_out(buffer) would typically look like:
Current configuration : 559 bytes
!
interface GigabitEthernet1/0/17
description blablabla
switchport mode access
switchport voice vlan xxxxx
no logging event link-status
authentication event fail retry 0 action authorize vlan xxxxx
authentication event no-response action authorize vlan xxxxx
authentication host-mode multi-domain
authentication port-control auto
authentication violation restrict
mab
no snmp trap link-status
dot1x pae authenticator
dot1x timeout tx-period 5
dot1x timeout supp-timeout 10
no mdix auto
spanning-tree portfast
service-policy input Access-Port
end
The "EXTRACT DESCRIPTION STRING FROM $expect_out(buffer)" line is the part that I am trying to figure out. I know how to split the line up to grab just the description, but i just do not know how to extract the line itself from the buffer variable.
Use the regexp command with the -line option:
% regexp -line {^\s*description (.*)$} $expect(buffer) -> desc
1
% puts $desc
blablabla
I assume the description is not multi-line.
Also, if you just need a boolean value,
set servicepolicy [string match "*service-policy input Access-Port*" $expect_out(buffer)]
or, do this
set servicepolicy [expr {[string match "*service-policy input Access-Port*" $expect_out(buffer)] ? "yes" : "no"}]

Using argument expansion {*} with more than one statement

I am using {*} in tcl for argument expansion and come across this issue.
#!/usr/bin/tclsh
set reset {
set count 0;
set age 24;
}
puts $reset
eval $reset; # This is working fine. Commented out the below line and tried
{*}$reset; # This is throwing error. Commented out the above line and tried.
if { [ info exists count ] && [ info exists age ] } {
puts "count & age initialzed to $count and $age"
} else {
puts "count & age not initialzed :("
}
As you see, I have the reset variable defined and I am evaluating with eval as well as with {*}.
While eval is working fine, {*} is throwing error as
wrong # args: should be "set varName ?newValue?"
while executing
"{*}$reset"
Then I have changed the variable reset as follows,
set reset {
set count 0;
}
i.e. I have removed the 2nd set statement and the code works fine.
Why {*} is working fine with one statement and not with more than that ? Or What I am missing here ?
Commands lite eval and uplevel (and while and if etc) can evaluate scripts, i.e. strings/lists that consist of zero or more command invocations. When you invoke {*}$reset you get six words instead of one, but the interpreter still expects a single command invocation. What actually gets invoked is the command set with five arguments, and the command will balk at that.
Expanding a word into several words works as long as the expansion forms exactly one command invocation:
set foo bar
# => bar
Too few words:
set cmd set
# => set
{*}$cmd
# => wrong # args: should be "set varName ?newValue?"
Too many words:
set cmd {set foo baz ; set abc def}
# => set foo baz ; set abc def
{*}$cmd
# => wrong # args: should be "set varName ?newValue?"
Just right:
set cmd {set foo}
# => set foo
{*}$cmd
# => bar
set cmd {set foo baz}
# => set foo baz
{*}$cmd
# => baz
Also just right:
set cmd set
# => set
{*}$cmd ghi jkl
# => jkl
set cmd {set mno}
# => set mno
{*}$cmd pqr
# => pqr
Documentation: eval, if, set, {*}, uplevel, while

Passing arguments in tcl procedure of ns2

Below is my simple procedure in tcl
set mymin 8
proc ann { mymin } {
puts stdout "$mymin is the answer"
}
ann
When i run this it keeps giving error as:
wrong # args: should be "ann mymin"
Please let me know what is the issue here. Thanks
You need to call ann with an argument because that's how you wrote your proc.
ann $mymin

Tcl global variables in proc declaration

So I have this piece of Tcl code that inherited. Essentially it does the following:
set LOG_ALL 0
set LOG_DEBUG 1
set LOG_INFO 2
set LOG_WARN 3
set LOG_ERROR 4
set LOG_FATAL 5
set LOG_SILENT 6
proc v2 {vimm {log LOG_DEBUG}} {
global LOG_DEBUG
if {$log == $LOG_DEBUG} {
puts "log"
} else {
puts "no log"
}
}
I suspect that the original idea of the designed was to use global variable for the default value of the log parameter. However, it isn't working as expected and I can't find how to write it correctly, assuming it even possible.
Which syntax will be correct?
Thank you for help.
Well, this would be correct:
proc v2 [list vimm [list log $LOG_DEBUG]] {
# ... body same as before
}
But that's just ugly. A neater way is:
proc v2 {vimm {log ""}} { # Any dummy value would do...
global LOG_DEBUG
if {[llength [info level 0]] < 3} {
set log $LOG_DEBUG
}
# ... as before
}
But the true Zen of Tcl is to not use numbers for this task at all, but rather names:
proc v2 {vimm {log "debug"}} {
if {$log eq "debug"} {
puts "log"
} else {
puts "no log"
}
}