TCL list element in quotes followed by } instead of space - string

I'm trying to write an automated validation test on a small program using TCL. It should evaluate the input h1=7 and pass if the output is 7.000000. Likewise, the input h1=9 should pass if the output is 9.0000. However, I get the following error:
ERROR: list element in quotes followed by "}" instead of space
while executing
"foreach pattern $testdata {
set inputs [ lindex $pattern 0 ]
set expected [ lindex $pattern 1 ]
eval "spawn $CLIC $inputs"
expect {..."
(file "./test/clic/test-clic.exp" line 22)
Here is the code:
#!/usr/bin/expect
set tool "clic"
set CLIC "./clic "
set testdata {
{"h1=7" "7.000000"}
{"h1=9" "9.000000"}
}
# global CLIC
foreach pattern $testdata {
set inputs [ lindex $pattern 0 ]
set expected [ lindex $pattern 1 ]
eval "spawn $CLIC $inputs"
expect {
$expected { pass $inputs }
default { fail $inputs }
}
}
How do I resolve this? Thank you.

Given the error message given and the discrepancy between the line number in the error message and the number of lines in the script you told us about, I'm guessing that you've trimmed down the script a little bit before asking the question. Which would be perfectly OK (and a good thing) except that in the trimming process you removed the thing that was causing the problem! The code that you posted doesn't have the issue.
The issue is almost certainly in one of the lines of testdata that you removed. It's either that you've got malformatted list as testdata, or that it produces a malformatted script when you do the concatenations for the eval "spawn …"; unfortunately, I can't be sure which with the info you've given us. (It's also possibly an issue in the expect with it not liking taking a value from a variable when that argument is in braces; the documentation for expect isn't very clear about this case, yet it gives hints that it might do what you want.)
A good start would be to update the script to actually use the features of Tcl 8.6 (or Tcl 8.5) since you're already using that version. The key changes happen to these lines:
foreach pattern $testdata {
set inputs [ lindex $pattern 0 ]
set expected [ lindex $pattern 1 ]
eval "spawn $CLIC $inputs"
Which are much better written as:
foreach pattern $testdata {
lassign $pattern inputs expected
spawn {*}$CLIC {*}$inputs
That has far fewer ways of being misinterpreted than what you were using before, as well as being shorter. We can also wrap that all up in code to give better error handling:
foreach pattern $testdata {
if {[catch {
lassign $pattern inputs expected
spawn {*}$CLIC {*}$inputs
} msg]} {
puts stderr "Problem handling pattern '$pattern': $msg"
continue
}
If you still get the same failure at that point, the problem is almost certainly that your overall testdata is a malformed list (and it would be malformed like this: "something"{something else} with no whitespace between closing quotes and opening braces); since that's under your complete control, you'll just have to fix it…

Related

How do you compare the value of an array to a variable in bash script?

I'm practicing bash and honestly, it is pretty fun. However, I'm trying to write a program that compares an array's value to a variable and if they are the same then it should print the array's value with an asterisk to the left of it.
#!/bin/bash
color[0]=red
color[1]=blue
color[2]=black
color[3]=brown
color[4]=yellow
favorite="black"
for i in {0..4};do echo ${color[$i]};
if {"$favorite"=$color[i]}; then
echo"* $color[i]"
done
output should be *black
There's few incorrect statements in your code that prevent it from doing what you ask it to. The comparison in bash is done withing square brackets, leaving space around them. You correctly use the = for string comparison, but should enclose in " the string variable. Also, while you correctly address the element array in the echo statement, you don't do so inside the comparison, where it should read ${color[$i]} as well. Same error in the asterisk print. So, here a reworked code with the fixes, but read more below.
#!/bin/bash
color[0]=red
color[1]=blue
color[2]=black
color[3]=brown
color[4]=yellow
favorite=black
for i in {0..4};do
echo ${color[$i]};
if [ "$favorite" = "${color[$i]}" ]; then
echo "* ${color[$i]}"
fi
done
While that code works now, few things that probably I like and would suggest (open to more expert input of course by the SO community): always enclose strings in ", as it makes evident it is a string variable; when looping an array, no need to use index variables; enclose variables always within ${}.
So my version of the same code would be:
#!/bin/bash
color=("red" "blue" "black" "brown" "yellow")
favorite="black"
for item in ${color[#]}; do
echo ${item}
if [ "${item}" = "${favorite}" ]; then
echo "* $item"
fi
done
And a pointer to the great Advanced Bash-Scripting Guide here: http://tldp.org/LDP/abs/html/

TCL factorial calculation code: extra characters after close-brace

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.

Syntax error near unexpected token `done' whis done written

I have a problem with the done.
It says I have some typo error but I can't figure what's wrong at all.
Here is the code:
#./bin/bash
until [$err == 0];
do
java -Xms512m -Xmx512m -cp lib/*:lib/uMad/*:mysql-connector-java-5.1.15-bin.jar:l2jfrozen-core.jar com.l2jfrozen.gameserver.GameServer
err=$?
sleep 5
done
Your shebang line is wrong. #./bin/bash will not execute bash.
It should read #!/bin/bash. You are probably using a shell other than bash to invoke this script.
Also, beware that the [$err == 0] line expands the value of $err, which is probably an empty string, unless it has been exported. If it's empty, this will result in an error, because Bash will be interpreting [ == 0].
The safest approach is this:
unset err
until [ "$err" == "0" ];
do
# etc...
done
From my experience when working with brackets and if loops, you need proper spacing and double, not single brackets. There needs to be space on each side of the double brackets with the exception of the semi-colon. Here is an example block:
#!/bin/bash
err=5
until [[ $err == 0 ]]; do
((err-=1));
echo -e "$err\n";
sleep 3
done
I do not see why the same would not apply to a do until loop.
You're probably aware but your heading has a period in it instead of a shebang.
#./bin/bash

Auto_execok problem on cygwin

I have a problem:
the auto_execok command doesn't work on Cygwin platform as expected.
It cannot find anything from your PATH enviroment variable, as
info body auto_execok
"...
foreach dir [split $path {;}] {
"
It thinks by default that ; is right separator, but Cygwin uses :!
How to elegantly overcome this problem?
I don't want to change PATH variable as other programs/scripts could correctly use : as it should be for Cygwin.
Have you got a proper Cygwin-aware build of Tcl? As you've found, a straight Windows build runs into problems precisely because the Cygwin environment is a sort of mix between Unix and Windows. (This an example of why we don't fully support doing things in Cygwin; it gets some love from time to time, but it's not a primary platform because it is fully of fiddly complexities.) That said, this is the sort of question which it is almost certainly better to ask on comp.lang.tcl as that's got a community likely to be able to help with this sort of thing.
Also, what patch-level of Tcl is this? This matters because the level of support has most certainly varied over time…
We can use a mix of set ar [info args auto_execok], set bd [info body auto_execok],
some regsub on body with set cygdir [exec cygpath -a $wdir] and eval proc auto_exeok {$ar} {$bd} to obtain needed result.
However, for the moment, I'm not yet ready with the complete solution.
You can wrap the native tcl version of auto_execok with one that will resolve the correct path. We can use the fact that the original auto_execok will find the cygpath.exe and in one fell swoop tell use that the current script is running windows and it is setup for cygwin. Once that it is known we can wrap the original auto_execok proc with one that will use cygpath.exe to resolve the true windows path. I have used the try command so this is for 8.5 and above but this can be written using catch for lower versions of tcl. Also because subst command is used the path to cygpath is hardcoded into the new auto_execok proc so lookup only happens once. Also only allow this code to run once. So as example
before code below runs
puts "[ auto_execok tar ]"
gives
"/usr/bin/tar"
after code is run auto_execok is wrapped:
puts "[ auto_execok tar ]"
gives (on my machine):
"C:/cygwin/bin/tar.EXE"
if { [string length [ auto_execok cygpath ] ] } {
set paths [ split $env(PATH) ";" ]
set cygexecpath ""
foreach p $paths {
set c [file join $p cygpath.exe ]
puts "checking for $c "
if {[file exists $c ] } {
set cygexecpath [file join $p cygpath.exe ]
break
}
}
if { $cygexecpath eq "" } {
puts "unable to find true path to [auto_execok cygpath.exe ]"
}
# rename original proc so we can use it in our wrapper proc
rename ::auto_execok ::auto_execok_orig
uplevel #0 [subst -nocommands {proc auto_execok { path } {
try {
set path [auto_execok_orig \$path ]
if { \$path ne \"\" } {
set path [string trim [exec $cygexecpath -w \$path ] ]
}
} on error { a b } {
set path \"\"
}
return \$path
} } ]
puts "[info body auto_execok ] "
}

Compare integer in bash, unary operator expected

The following code gives
[: -ge: unary operator expected
when
i=0
if [ $i -ge 2 ]
then
#some code
fi
why?
Your problem arises from the fact that $i has a blank value when your statement fails. Always quote your variables when performing comparisons if there is the slightest chance that one of them may be empty, e.g.:
if [ "$i" -ge 2 ] ; then
...
fi
This is because of how the shell treats variables. Assume the original example,
if [ $i -ge 2 ] ; then ...
The first thing that the shell does when executing that particular line of code is substitute the value of $i, just like your favorite editor's search & replace function would. So assume that $i is empty or, even more illustrative, assume that $i is a bunch of spaces! The shell will replace $i as follows:
if [ -ge 2 ] ; then ...
Now that variable substitutions are done, the shell proceeds with the comparison and.... fails because it cannot see anything intelligible to the left of -gt. However, quoting $i:
if [ "$i" -ge 2 ] ; then ...
becomes:
if [ " " -ge 2 ] ; then ...
The shell now sees the double-quotes, and knows that you are actually comparing four blanks to 2 and will skip the if.
You also have the option of specifying a default value for $i if $i is blank, as follows:
if [ "${i:-0}" -ge 2 ] ; then ...
This will substitute the value 0 instead of $i is $i is undefined. I still maintain the quotes because, again, if $i is a bunch of blanks then it does not count as undefined, it will not be replaced with 0, and you will run into the problem once again.
Please read this when you have the time. The shell is treated like a black box by many, but it operates with very few and very simple rules - once you are aware of what those rules are (one of them being how variables work in the shell, as explained above) the shell will have no more secrets for you.
Judging from the error message the value of i was the empty string when you executed it, not 0.
I need to add my 5 cents. I see everybody use [ or [[, but it worth to mention that they are not part of if syntax.
For arithmetic comparisons, use ((...)) instead.
((...)) is an arithmetic command, which returns an exit status of 0 if
the expression is nonzero, or 1 if the expression is zero. Also used
as a synonym for "let", if side effects (assignments) are needed.
See: ArithmeticExpression
Your piece of script works just great. Are you sure you are not assigning anything else before the if to "i"?
A common mistake is also not to leave a space after and before the square brackets.

Resources