TCL factorial calculation code: extra characters after close-brace - cygwin

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.

Related

Positional Parameters in C-shell

I am unable to print positional parameters using this shell command: echo $1.
I am using it as following two commands:
% set hi how are you
% echo $1
Nothing get out of the command, but hi should be print.
In csh, you need to assign to the argv array:
> set argv=(hi how are you)
> echo $1
hi
Explanation:
argv is an array variable which contains the command line argument list (the 0th argument is name as the shell was invoked and the other start from 1th index). Variables $0 - $n also contain values of the arguments . So $argv[1] is the same as $1. To assign to an array variable, you can use either set arr=(value1 value2) or set arr[1] = value1.
set value1 value2 would work in bash, but csh is meant to be similar to the C language, therefore the argv array is used (read a little about C program command line arguments if you don't know why).
But in csh, this: set first second means assigning an empty (null) value to the variables first and second.

Understand the use of braces and parenthesis in if

In some of the code I was going through I found that if was using braces {} for someplace and parenthesis (()) for some other. Can someone tell me the exact meaning and where to use which one?
if [ "$1" = "--help" ]
if (( $# != 3 ))
The bracket [ is a built-in command of the shell; you can also call it as test:
if [ a = b ]
then ...
equals:
if test a = b
then ...
The syntax of the test command is rather text-oriented (see the bash man page for details at chapter CONDITIONAL EXPRESSIONS).
The braces {…} are shell syntax and used for grouping commands (without creating a subshell):
{ date; ls; echo $$; } > 1>&2
This will execute date, ls, and echo $$ and redirect all their output to stderr.
The parenthesis (…) are shell-syntax and used for creating a subshell:
(date; ls; echo $$) > 1>&2
Like above but the PID ($$) given out is that of the subshell.
The difference between grouping and subshell is delicate (and out of scope here).
The doubled brackets [[…]] are shell syntax but otherwise behave like the single bracket [ command. The only difference is for using < etc. for string comparison and locale support.
The doubled parentheses ((…)) are equivalent to using the let builtin shell command. They basically allow number-oriented expressions to be evaluated (ARITHMETIC EVALUATION). < and > sort numerically (instead of lexicographically) etc. Also, in some constructs like for ((i=0; i<10; i++)); do echo "$i"; done they are used as a fixed syntax.
Dollar-parenthesis $(…) result in the output of the command they enclose:
echo "$(date)" # a complicated way to execute date
Dollar-brackets $[…] are deprecated and should be replaced by dollar-double-parentheses.
Dollar-double-parentheses $((…)) result in the value of the numerical expression they enclose:
echo "$((4 + 3 * 2))" # should print 10
Dollar-braces ${…} result in the variable expansion they enclose. In the simplest case this is just a variable name, then they evaluate to the variable value:
a=foo
echo "${a}" # prints foo
This can (and often is) abbreviated by stripping the braces: $a
But it also can be more complex like ${a:-"today is $(date)"}. See Parameter Expansion in the bash man page for details.
Redirection-parenthesis <(…) and >(…) create a subprocess, a file descriptor its output/input is associated with, and a pseudo file associated with that descriptor. It can be used to pass the output of a program as a seeming file to another program: diff <(sleep 1; date) <(sleep 2; date)

tcl command to search for a particular word inside a txt file in linux

I need to write a tcl script that will process the lines of a text file. The file is looks like
10.77.33.247 10.77.33.241 rtp 0x26
10.77.33.247 10.77.33.241 rtp 0x26
10.77.33.247 10.77.33.241 rtp 0x26
10.77.33.247 10.77.33.241 0x24
10.77.33.247 10.77.33.241 0x22
10.77.33.247 10.77.33.241 0x21
I need to be able to iterate through the file and for each line that contains rtp store the value that comes after it (e.g., 0x26 in the sample above) in a variable to do use in other parts of the script.
Here's a (rather low-level) Tcl way to do it.
set ch [open myfile.txt]
set data [chan read $ch]
chan close $ch
set lines [split [string trim $data] \n]
set res {}
foreach line $lines {
if {[llength $line] > 3 && [lindex $line 2] eq {rtp}} {
lappend res [lindex $line 3]
}
}
If you replace "myfile.txt" with the name of your data file and run this code, you get the words you were after collected in the variable res.
Explanation
It's usually best to use standard (builtin or tcllib) commands, such as fileutil::foreachLine in glenn jackman's answer. If one wants to do it step by step, however, Tcl still makes it very easy.
The first step is to get the contents of the file into memory. There is a standard command for that too: fileutil::cat, but the following sequence will do:
set ch [open myfile.txt]
set data [chan read $ch]
chan close $ch
(This is more or less equivalent to set data [fileutil::cat myfile.txt].)
Next step is to split the text into lines. It's always a good idea to trim off whitespace at both ends of the text, otherwise loose newlines can create empty elements that disturb processing.
set lines [split [string trim $data] \n]
In some cases, we might have to split the lines into lists of fields, but from the example it seems that the lines are already usable as lists (lines that only have whitespace, alphanumerics, and well-behaved punctuation such as dots usually are).
We need a test for matching lines. There are several alternatives that fit the example data you provided, including
string match *rtp* $line ;# match every line that has "rtp" somewhere
[llength $line] > 3 ;# match every line that has more than three columns
[lindex $line 2] eq {rtp} ;# match every line where the third element is "rtp"
We also need a way to extract the data we want. If the word after "rtp" is always in the last column, [lindex $line end] will do the job. If the word is always in the fourth column, but there may be further columns, [lindex $line 3] is better.
Grabbing a couple of these alternatives, the procedure to get a list of words as specified can be written
set res {}
foreach line $lines {
if {[llength $line] > 3 && [lindex $line 2] eq {rtp}} {
lappend res [lindex $line 3]
}
}
(In pseudo-code: get an empty list (res); test every line (using a combination of two of the tests above), extract the sought-after word from every matching line and add it to the res list.)
or, using lmap (Tcl 8.6+)
set res [lmap line $lines {
if {[llength $line] > 3 && [lindex $line 2] eq {rtp}} {
lindex $line 3
} else {
continue
}
}]
All the words that came after a "rtp" word should now be in res. If you just wanted the last match, it's [lindex $res end].
Documentation: chan, continue, foreach, if, lappend, lindex, llength, lmap, open, set, split, string
Supposing your file is foo.txt:
grep "word" foo.txt
grep "0x26" file.txt
will show you all the lines with 0x26 in them.
tcllib has lots of goodness in it:
% package require fileutil
1.14.5
% fileutil::foreachLine line "file" {
if {[string match {*rtp*} $line]} {
lappend values [lindex [split $line] end]
}
}
% puts $values
0x26 0x26 0x26
The below code works for getting the text from the file which is in cotes (" "):
proc aifWebcamInitVideo {} {
variable devicePath "c:/testfile.txt"
#ffmpeg command to get the device name connected to system
exec ffmpeg -list_devices true -f dshow -i dummy >& $devicePath &
after 4000 ;# wait of 4 seconds so that device can be selected.
set files [glob $aif::LogRootDir/*] ;# Look for all the files.
foreach file $files {
set fileName $devicePath
if {[string match $fileName $file ] } { ;# this if statement check currently captured video with the files present in directory.
set file [open $devicePath ] ;# open the file
set file_device [read $file]
set data [split $file_device "\n"] ;# divides the open file into lines.
foreach line $data {
if {[regexp {"([^""]*)"} $line -> substring]} { ;# check for the quotes to retrieve the device connected to system.
set result $substring
lappend cameraList $result ;# makes the list of devices.
set camera [lindex $cameraList 0]
}
}
close $file
break
}
}
#values passed to FFMPEG command.
variable TableCamera $camera
puts "Device selected for Video capture is : $TableCamera" ;# gets the first device from the list
}

TCL list element in quotes followed by } instead of space

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…

Parameters in TCL using ns2

how can i sent this values
24.215729
24.815729
25.055134
27.123499
27.159186
28.843474
28.877798
28.877798
to tcl input argument?
as you know we cant use pipe command because tcl dosent accept in that way!
what can i do to store this numbers in tcl file(the count of this numbers in variable and can be 0 to N and in this example its 7)
This is pretty easy to do in bash, dump the list of values into a file and then run:
tclsh myscript.tcl $(< datafilename)
And then the values are accessible in the script with the argument variables:
puts $argc; # This is a count of all values
puts $argv; # This is a list containing all the arguments
You can read data piped to stdin with commands like
set data [gets stdin]
or from temporary files, if you prefer. For example, the following program's first part (an example from wiki.tcl.tk) reads some data from a file, and the other part then reads data from stdin. To test it, put the code into a file (eg reading.tcl), make it executable, create a small file somefile, and execute via eg
./reading.tcl < somefile
#!/usr/bin/tclsh
# Slurp up a data file
set fsize [file size "somefile"]
set fp [open "somefile" r]
set data [read $fp $fsize]
close $fp
puts "Here is file contents:"
puts $data
puts "\nHere is from stdin:"
set momo [read stdin $fsize]
puts $momo
A technique I use when coding is to put data in my scripts as a literal:
set values {
24.215729
24.815729
25.055134
27.123499
27.159186
28.843474
28.877798
28.877798
}
Now I can just feed them into a command one at a time with foreach, or send them as a single argument:
# One argument
TheCommand $values
# Iterating
foreach v $values {
TheCommand $v
}
Once you've got your code working with a literal, switching it to pull the data from a file is pretty simple. You just replace the literal with code to read a file:
set f [open "the/data.txt"]
set values [read $f]
close $f
You can also pull the data from stdin:
set values [read stdin]
If there's a lot of values (more than, say, 10–20MB) then you might be better off processing the data one line at a time. Here's how to do that with reading from stdin…
while {[gets stdin v] >= 0} {
TheCommand $v
}

Resources