How to check the string value of an argument passed to expect script? - tcl

I'm writing a expect script that takes command line arguments. I would like to be able to detect whether the first argument is "--help" and print a Usage string then. Otherwise use the argument as a port number with a specific default (let's say 1818).
I tried this code that fails:
#!/usr/bin/expect
if {[llength $argv] != 1} {
puts "No Port number specified, defaulting to port 1818."
set port 1818
} else {
if {[lindex $argv 0] eq "--help"} {
puts "Usage: testit [--help] [port]"
exit
} else {
set port [lindex $argv 0]
}
}
The error is:
invalid command name "--help"
while executing
"--help"
invoked from within
"if {[llength $argv] != 1} {
puts "No Port number specified, defaulting to port 1818."
set port 1818
} else {
if {[lindex $argv 0] eq "--he..."
Obviously it is trying to interpret the content of the "--help" string while I'm trying to make the script compare the value of argument 0 to "--help".
What is wrong in the above logic or syntax?
I tried using other strings, like "help" instead of "--help" but the outcome is the same.
I'm not that familiar with expect and tcl, but I tried the expression in tclsh and the same thing happens there. So this issue has to do with invalid tcl code. The following tcsh session shows that the syntax if {$variable=="--help"} {...} is OK, but removing the white space in my string comparison attempt above does not solve the problem.
Here's the tcsh session:
% set v1 "--help"
--help
% if [v1 == "--help'] { puts "allo"}
extra characters after close-quote
% if [v1 == "--help"] { puts "allo"}
invalid command name "v1"
% if [$v1 == "--help"] { puts "allo"}
invalid command name "--help"
% if $v1 == help {puts "allo"}
invalid bareword "help"
in expression "--help";
should be "$help" or "{help}" or "help(...)" or ...
% if $v1 == "--help" {puts "allo"}
invalid bareword "help"
in expression "--help";
should be "$help" or "{help}" or "help(...)" or ...
% if {$v1=="--help"} {puts "allo"}
allo
%

The problem is this line:
puts "Usage: testit [--help] [port]"
And the problem with it is that [...] does command substitution in that situation. You need to add a couple of backslashes in there to prevent that, like this:
puts "Usage: testit \[--help] \[port]"
Or you can enclose the string in braces to inhibit all substitutions:
puts {Usage: testit [--help] [port]}
Either will work (and they'll get compiled to exactly the same thing so use whichever you prefer).

Related

Expect - avoid sending escape prompt sequences via ssh

The script is intended to retrieve the contents of some directory when it is getting full.
For development, the 'full' was set at 15%, the directory is /var/crash.
expect "#*" {
foreach part $full {
puts "part: $part"
set dir [split $part]
puts "dir: $dir [llength $dir]"
set d [lindex $dir 0]
puts "d: $d"
send -s -- "ls -lhS $d\n"
expect "#*" { puts "for $dir :: $expect_out(buffer)"}
}
}
send "exit\r"
The output of the script is:
part: /var/crash 15%
dir: {/var/crash} 15% 2
d: /var/crash
send: sending "ls -lhS \u001b[01;31m\u001b[K/var\u001b[m\u001b[K/crash\n" to { exp7 }
expect: does "" (spawn_id exp7) match glob pattern "#*"? no
expect: does "ls -lhS \u00071;31m\u0007/var\u0007\u0007/" (spawn_id exp7) match glob pattern "#*"? no
expect: does "ls -lhS \u00071;31m\u0007/var\u0007\u0007/crash\r\n" (spawn_id exp7) match glob pattern "#*"? no
As can be seen, although $d is /var/crash, when it is sent via ssh it becomes something like \u001b[01;31m\u001b[K/var\u001b[m\u001b[K/crash.
I cannot change the remote machine definitions for the command prompt.
How to get rid of these escape sequences that are sent?
Edit: Info about $full as requested
The proc analyze just tries to filter meaningful data.
proc analyze_df {cmd txt} {
set full [list]
set lines [split $txt \n]
foreach l $lines {
if {[string match $cmd* $l]} { continue }
set lcompact [regsub -all {\s+} $l " "]
set data [split $lcompact]
if {[string match 8?% [lindex $data 4]] \
|| [string match 9?% [lindex $data 4]] \
|| [string match 1??% [lindex $data 4]] \
|| [string match 5?% [lindex $data 4]] \
|| [string match 1?% [lindex $data 4]] } {
lappend full "[lindex $data 5] [lindex $data 4]"
}
}
return $full
}
The extract about the $full that was missing.
set command0 "df -h | grep /var"
send -- "$pass\r"
expect {
-nocase "denied*" {puts "$host denied"; continue}
-nocase "Authentication failed*" {puts "$host authentication failed"; continue}
"$*" {send -s -- "$command0\n"}
timeout {puts "$host TIMEOUT"; continue}
}
expect "$*" {puts "$host -> $expect_out(buffer)" }
set full [analyze_df $command0 $expect_out(buffer)]
Taking the suggestion received, perhaps it's grep that is adding the escape sequences, no?
You don't show how $full gets its value. But it must already have the escape codes. When printing $d those escape codes are interpreted by the terminal, so they may not be obvious. But Expect/Tcl definitely doesn't insert them. This is also confirmed by the braces around the first element when you print $dir. If this element was plain /var/crash, there would be no braces.
Your remark about the command prompt would suggest that $full may be taken from there. Maybe you cannot permanently change the remote machine's command prompt, but you should be able to change it for your session by setting the PS1 environment variable.
Another trick that may help in such situations is to do set env(TERM) dumb before spawning the ssh command. If the prompt (or other tools) correctly use the tput command to generate their escape codes, a dumb terminal will result in empty strings. This won't work if the escape codes are hard-coded for one specific TERM. But that's a bug on the remote side.
If you're absolutely stuck with that input data (and can't tell things to not mangle it with those ANSI terminal colour escape codes) then you can strip them out with:
set dir [split [regsub -all {\u001b[^a-zA-z]*[a-zA-Z]} $part ""]]
This makes use of the fact that the escape sequences start with the escape character (encoded as \u001b) and continue to the first ASCII letter. Replacing them all with the empty string should de-fang them cleanly.
You are recommended to try things like altering the TERM environment variable before calling spawn so that you don't have to do such cleaning. That tends to be easier than attempting to "clean up" the data after the fact.

Why can't I access errorInfo and errorCode

I have the following code:
$ cat ~/tmp/2.tcl
set zero 0
proc p1 {} {
if {[catch {expr 1/$zero} err]} {
puts "errorCode=$errorCode"
puts "errorInfo=$errorInfo"
}
}
p1
When I source it, I get error accessing errorCode:
$ tclsh ~/tmp/2.tcl
can't read "errorCode": no such variable
while executing
"puts "errorCode=$errorCode""
(procedure "p1" line 3)
invoked from within
"p1"
(file "~/tmp/2.tcl" line 9)
I tried changing to $::errorCode, but did not help.
Can you see what is wrong?
The errorInfo and errorCode variables are globals. You should either use the global command to bring them into scope or use their fully-qualified names (i.e., precede with ::).
It might be easier to pick the information out of the result options dictionary (a new feature in 8.5).
Starting from Tcl 8.5 [catch] doesn't set the errorCode and errorInfo global variables. (As Donal has pointed out, it still does, so they can be accessed as $::errorCode and $::errorInfo). And in addition it puts their values into a dictionary which name is to be specified as the third argument. The following code
#!/usr/bin/tclsh
set zero 0
proc p1 {} {
if {[catch {expr 1/$zero} err opts] == 1} {
puts "errorCode=[dict get $opts -errorcode]"
puts "errorInfo=[dict get $opts -errorinfo]"
}
}
p1
prints
errorCode=NONE
errorInfo=can't read "zero": no such variable
while executing
"expr 1/$zero"
in Tcl 8.5.19, and
errorCode=TCL READ VARNAME
errorInfo=can't read "zero": no such variable
while executing
"expr 1/$zero"
in Tcl8.6.6.
You'd probably want to use $::zero in the division after which the result would be
errorCode=ARITH DIVZERO {divide by zero}
errorInfo=divide by zero
while executing
"expr 1/$::zero"

TCL assign variable from a command

In TCL/Expect,
I can iterate the command line parameter in expect script.
foreach arg $argv {
puts "This is your args: $arg"
}
If I want to create a variable which store the size-of /length-of $argv
, how do I do that? The following command doesn't seem to work.
set argSize llength $argv
Woops missing the square bracket as the "executable". After that, it works. Giving up too soon :-)
set argSize [llength $argv]
puts "argSize $argSize"

How to pass arguments to tcl scripts when using tclsh [duplicate]

This is the code in TCL that is meant to produce factorial of a number given as parameter by the user.
if {$argc !=1}{
puts stderr "Error! ns called with wrong number of arguments! ($argc)"
exit 1
} else
set f [lindex $argv 0]
proc Factorial {x}{
for {set result 1} {$x>1}{set x [expr $x - 1]}{
set result [expr $result * $x]
}
return $result
}
set res [Factorial $f]
puts "Factorial of $f is $res"
There is a similar SO question, but it does not appear to directly address my problem. I have double-checked the code for syntax errors, but it does not compile successfully in Cygwin via tclsh producing the error:
$ tclsh ext1-1.tcl
extra characters after close-brace
while executing
"if {$argc !=1}{
puts stderr "Error! ns called with wrong number of arguments! ($argc)"
exit 1
} else
set f [lindex $argv 0]
proc Factorial {x}{..."
(file "ext1-1.tcl" line 3)
TCL Code from: NS Simulator for Beginners, Sophia-Antipolis, 2003-2004
Tcl is a little bit more sensitive about whitespace than most languages (though not as much as, say, Python). For instance, you can't add unescaped newlines except between commands as command separators. Another set of rules are that 1) every command must be written in the same manner as a proper list (where the elements are separated by whitespace) and 2) a command invocation must have exactly the number of arguments that the command definition has specified.
Since the invocation must look like a proper list, code like
... {$x>1}{incr x -1} ...
won't work: a list element that starts with an open brace must end with a matching close brace, and there can't be any text immediately following the close brace that matches the initial open brace. (This sounds more complicated than it is, really.)
The number-of-arguments requirement means that
for {set result 1} {$x>1}{incr x -1}{
set result [expr $result * $x]
}
won't work because the for command expects four arguments (start test next body) and it's only getting two, start and a mashup of the rest of other three (and actually not even that, since the mashup is illegal).
To make this work, the arguments need to be separated:
for {set result 1} {$x>1} {incr x -1} {
set result [expr {$result * $x}]
}
Putting in spaces (or tabs, if you want) makes the arguments legal and correct in number.

Compilation Error when running Echo Service example from Tcl Book

Doing the echo service example in the book, 'Practical Programming in Tcl & TK 4th edition' Brent B. Welch Ken Jones Jeffrey Hobbs.
Its on page 241, example 17-3. Copied it straight out of the book and its giving me the following error:
tclsh "theEchoService.tcl" (in directory: /home/<username>/Documents/Scripts/tcl)
Compilation failed.
wrong # args: should be "proc name args body"
while executing
"proc Echo {sock} \
{
global echo
if {[eof $sock]} || [catch {gets $sock line}]} \
{
;# end of file or abnormal connection drop
close $sock
pu..."
(file "theEchoService.tcl" line 16)
Heres my full code:
#!/usr/bin/tclsh
;#The Echo Service. Socket ProgrammingPage 241, Example 17-3
proc Echo_Server {port} \
{
global echo
set echo(main) [socket -server EchoAccept $port]
}
proc EchoAccept {sock addr port} \
{
global echo
puts "Accept $sock from $addr $port"
set echo(addr, $sock) [list $addr $port]
fconfigure $sock -buffering line
fileevent $sock readable [list Echo $sock]
}
proc Echo {sock} \
{
global echo
if {[eof $sock]} || [catch {gets $sock line}]} \
{
;# end of file or abnormal connection drop
close $sock
puts "Close $echo(addr, $sock)"
unset echo(addr,$sock)
} \
else \
{
if {[string compare $line "quit"] == 0} \
{
;# Prevent new connections, Existing connections stay open
close $echo(main)
}
puts $sock $line
}
}
I've tried it without my escapes and still the same. Any ideas?
The problem is this line:
if {[eof $sock]} || [catch {gets $sock line}]} \
^^^
That extra } is terminating the if early, which makes the } at the end of the line terminate the body of the procedure early (and the rest of what you think the body is appears as extra arguments to proc, which doesn't like it).
You're recommended to avoid using backslashes to introduce newlines like that; it's just extra visual noise. You're also recommended to use an editor which can do auto-indentation and/or bracket matching, both of which would have helped you find your problem virtually immediately.
Don't try and make your Tcl code look like C. It is not the same language at all. Every statement in Tcl is made up of words terminated by a newline or semicolon. One way to group words is using the curly braces but these need to be on the same line as the earlier part of the phrase. You can escape the newline as it looks like you are attempting to do but this is fragile and hard to maintain because if you introduce any whitespace after your escape character you no longer escape the newline and you get the error you are seeing.
In Tcl code, put the opening braces on the same line as the code. eg:
proc Echo {sock} {
if {1 == 0} {
puts "something"
} else {
puts "do something else"
expr {
1 * 2 +
3
}
}
}
This is explained in detail in the Tcl(1) manual page but you have to read it rather carefully to glean the details.