Split Lines seperated by spaces to arrays - tcl

I have a text file that contains output from a program. It reads like this:
1 2
23 24
54 21
87 12
I need the output to be
arr[1]=2
arr[23]=24
arr[54]=21
arr[87]=12
and so on.
Each line is seperated by a space. How can I parse the lines to the array format as described above, using TCL? (I am doing this for NS2 by the way)

With awk:
awk '{ print "arr[" $1 "]=" $2 }' filename

You have mentioned that each line is separated by space, but gave the content separated by new line. I assume, you have each line separated by new line and in each line, the array index and it's value are separated by space.
If your text file contains only those texts given as below
1 2
23 24
54 21
87 12
then, you first read the whole file into a string.
set fp [open "input.txt" r]
set content [ read $fp ]
close $fp
Now, with array set we can easily convert them into an array.
# If your Tcl version less than 8.5, use the below line of code
eval array set legacy {$content}
foreach index [array names legacy] {
puts "array($index) = $legacy($index)"
}
# If you have Tcl 8.5 and more, use the below line of code
array set latest [list {*}$content]
foreach index [array names latest] {
puts "array($index) = $latest($index)"
}
Suppose if your file has some other contents along with these input contents, then you can get them alone using regexp and you can add elements to the array one by one with the classical approach.

You can use this in BASH:
declare -A arr
while read -r k v ; do
arr[$k]=$v
done < file
Testing:
declare -p arr
declare -A arr='([23]="24" [54]="21" [87]="12" [1]="2" )'

Related

TCL script to regroup csv file data

I am new to tcl scripting.
I have 2 columns in my CSV file.
Example: My data
A 12
D 18
B 33
A 11
D 49
I would like to extract column 2 values using Column 1 data.
Required output:
A: 12,11
B: 33
D: 18, 49
Can someone please help me to write a tcl script to perform this task?
Thank you in advance.
This is a very well known problem, and Tcl's dict lappend is exactly the tool you need to solve it (together with csv::split from tcllib to do the parsing of your input data).
package require csv
# Read split and collect
set data {}
while {[gets stdin line] >= 0} {
lassign [csv::split $line] key value; # You might need to configure this
dict lappend data $key $value
}
# Write out
puts ""; # Blank header line
dict for {key values} $data {
puts [format "%s: %s" $key [join $values ", "]]
}
The above is written as a stdin-to-stdout filter. Adapting to work with files is left as an exercise.

When using a variable, that is called from a file, prints variable name itself. How to resolve this?

I have following scenario:
2000::$var/22
2000:400::$var/22
2000:800::$var/22
I want store the above 3 lines in text file. and call one by one to print 1 to 15 as shown below.
2000::1/22
2000::2/22
2000::3/22
2000::4/22
2000::5/22
.
.
.
2000::15/22
When I import each line from a file, its not printing as shown above. Its prints variable name itself as shown below.
2000::$var/22
2000::$var/22
Please guide me, How to resolve this issue.
Thanks,
Balu P.
TCL has a command called subst which will do what you want. All you have to do is to set the value for var, then call subst:
set fileHandle [open data.txt]
while {[gets $fileHandle line] != -1} {
for {set var 1} {$var <= 15} {incr var} {
puts [subst -nocommand $line]
}
}
Discussion
The while loop will read the file, line by line, until the end of the file
Within the while loop, I set the variable var to 1 .. 15
Then I call subst to do substitution before printing it out
Update
Per Glenn Jackman's excellent suggestion, I updated the code with -nocommand to prevent command expansion, which could open door for malicious code.

Looking for a search string in a file and using those lines for processing in TCL

To be more precise:
I need to be looking into a file abc.txt which has contents something like this:
files/f1/atmp.c 98 100
files/f1/atmp1.c 89 100
files/f1/atmp2.c !! 75 100
files/f2/btmp.c 92 100
files/f2/btmp2.c !! 85 100
files/f3/xtmp.c 92 100
The script needs to find "!!" and use those lines to print out the following as output:
atmp2.c 75
btmp2.c 85
Any help?
this should do the trick.
set data {files/f1/atmp.c 98 100
files/f1/atmp1.c 89 100
files/f1/atmp2.c !! 75 100
files/f2/btmp.c 92 100
files/f2/btmp2.c !! 85 100
files/f3/xtmp.c 92 100}
set lines [split $data \n]
foreach line $lines {
set match [regexp {(\S+)\s+!!\s+(\d+)} $line -> file num]
if {$match} {puts "$file $num"}
}
Although regexp has a -all switch I don't think we can use it here as we only get the last match vars with -all
If your file isn't huge, you can slurp the whole thing into memory, split the lines into a TCL list, and then iterate through the list looking for a match. For example:
set fh [open foo]
set lines [read $fh]
close $fh
set lines [split $lines "\n"]
foreach line $lines {
if { [regexp {.*/(\S+\.c)\s*!!\s*(\d+)} $line match file data] } {
puts "$file $data"
}
}
This will successfully return just the lines with "!!" in them. With your posted corpus, the results are:
atmp2.c 75
btmp2.c 85
I might be tempted in this case to exec to awk:
set output [exec awk {$2 == "!!" {print $1, $3}} abc.txt]
puts $output
The trick is to combine the code that reads lines from the file with a regular expression that detects matching lines and extracts the relevant parts (a one-step process with regexp). The only tricky part is working out what exactly to use as the regular expression, so that you get exactly what you want. I'm going to guess that you're after the parts of the filenames after the /, that those filenames won't contain spaces, and that the number you're after is the entirety of the first digit sequence after the double exclamation. (Other formats are possible, some of which are easier to extract with other tools such as scan.) That would give us something like this:
set f [open abc.txt]
while {[gets $f line] >= 0} {
if {[regexp {([^\s/]+)\s+!!\s+(\d+)} $line -> name value]} {
# Or do whatever you want with these
puts "$name $value"
}
}
close $f
(The gets command with two arguments returns the length of line read, or -1 on failure. For normal files the only failure mode is EOF, so we can just terminate the loop when we get a negative value. Other kinds of channels can be more complex…)

How to manipulate each line in a file in TCL

I'm trying to write some data from iperf to a file using tcl script.The file has more than 100 lines. Now i need to parse the first 10 lines, neglect it and consider the next set of 10 lines and print it, again i need to neglect the next set of 10 lines and print the next 10 lines and keep continuing until i reach the end of file. How could i do it programmatic ally?
exec c:\\iperf_new\\iperf -c $REF_WLAN_IPAddr -f m -w 2M -i 1 -t $run_time > xx.txt
set fp [open "xx.txt" r ]
set file_data [read $fp]
set data [split $file_data "\n"]
foreach line $data {
if {[regexp {(MBytes) +([0-9\.]*)} $line match pre tput]==1 } {
puts "Throughput: $tput Mbps"
}
Well, as your example shows, you have found out how to split a (slurped) file into lines and process them one-by-one.
Now what's the problem with implementing "skip ten lines, process ten lines, skip another ten lines etc"? It's just about using a variable which counts lines seen so far plus selecting a branch of code based on its value. This approach has nothing special when it comes to Tcl: there are commands available to count, conditionally select branches of code and control looping.
If branching based on the current value of a line counter looks too lame, you could implement a state machine around that counter variable. But for this simple case it looks like over-engeneering.
Another approach would be to pick the necessary series of lines out of the list returned by split using lrange. This approach might use a nice property of lrange which can be told to return a sublist "since this index and until the end of the list", so the solution really boils down to:
set lines [split [read $fd] \n]
parse_header [lrange $lines 0 9]
puts [join [lrange $lines 10 19] \n]
parse_something_else [lrange 20 29]
puts [join [lrange $lines 30 end] \n]
For a small file this solution looks pretty compact and clean.
If I understood you correctly, you want to print lines 11-20, 31-40, 51-60,... The following will do what you want:
package require Tclx
set counter 0
for_file line xxx.txt {
if {$counter % 20 >= 10} { puts $line }
incr counter
}
The Tclx package provides a simple way to read lines from a file: the for_file command.

How to ensure my regular expression does not match too much

A file has few words with numbers in the begining of them. i want to extract a particular no line.when given 1, it extracts line 1 also with 11, 21
FILE.txt has contents:
1.sample
lines of
2.sentences
present in
...
...
10.the
11.file
when Executed pro 1 file.txt
gives results from line 1,10 and also from line 11
as these three results have 1 in their string. i.e
Output of the script:
1.sample
10.the
11.file
Expected output: the output which i am expecting
is only line 1 contents and not the line 10 or line 11 contents.
i.e
Expected output:
1.sample
My current code:
proc pro { pattern args} {
set file [open $args r]
set lnum 0
set occ 0
while {[gets $file line] >=0} {
incr lnum
if {[regexp $pattern $line]} {
incr occ
puts "The pattern is present in line: $lnum"
puts "$line"
} else {
puts "not found"
}
}
puts "total number of occurencese : $occ"
close $file
}
the program is working fine but the thing is i am retrieving lines that i dont want to along with the expected line. As the number (1) which i want to retrieve is present in the other strings such as 11, 21, 14 etc these lines are also getting printed.
kindly tolerate my unclear way of explaining the question.
You can solve the problem using word boundaries as suggested by glen but you can also consider the following things:
If after every line number there is a . then you can use it as delimiter in regular expression
regexp "^$lineNo\\." $a
I would also suggest to use ^ (match at the beginning of line) so that even if number is present in the line elsewhere it would not get counted.
tcl word boundaries are well explained at http://www.regular-expressions.info/wordboundaries.html
You have to ensure your pattern matches only between word boundaries:
if {[regexp "\\m$pattern\\M" $line]} { ...
See the documentation for regular expression syntax.
If what you're looking to do is as constrained as what you're describing, why not just use something like
if { [string range $line 0 [string length $pattern]] eq "${pattern}." } {
...
}