how can I convert
set var "USE_90a_Sc_ttv"
to
set out "9.0a ttv Sc"
using tcl code?
Regards,
Divesh
Use the split, lassign and regsub functions:
lassign [split $var _] prefix version type tag
regsub {(\d(\w)?)$} $version {.\1} nversion
set out "$nversion $tag $type"
If you are using an older version and don't have lassign available, you
can use lindex to retrieve specific items from the list returned by split.
set tlist [split $var _]
set version [lindex $tlist 1]
set type [lindex $tlist 2]
set tag [lindex $tlist 3]
regsub {(\d(\w)?)$} $version {.\1} nversion
set out "$nversion $tag $type"
I'd use scan to parse that, and list to assemble the results.
set var "USE_90a_Sc_ttv"
# Remember to check the result of [scan] for number of parsed fields
if {[scan $var {USE_%1d%2[^_]_%2[^_]_%3s%c} a b c d e] != 4} {
error "Unexpected input data! '$var'"
}
set out [list $a.$b $d $c]
Putting a %c at the end of the format lets me detect if there are any unexpected characters at the end. There shouldn't be; only 4 fields ought to be satisfied. This makes for a quick way to check that what I've got is what I expect. Also, %2[^_] is an unusual field specifier, but all it does is ask for 2 non-underscore characters.
Related
I have a string like this : abc0__remote_contr_major_abc__remote_hjk_klo_hcf_uio__apple_b_0_t_boo_dfs
I need to extract apple followed by everything until t_ and use that as a variable.
for example; if the string goes through the code, I am expecting apple_b_0_t as my output. I tried split and lindex but didnt work out.
set s "abc0__remote_contr_major_abc__remote_hjk_klo_hcf_uio__apple_b_0_t_boo_dfs"
set prefix [split $s "__"]
set c [lindex $prefix 4]
So ended up doing this and it worked but I am wondering if there is a easier/generic solution
set prefix [join [lrange [split $tile_dfx_fclk "__"] 12 15] _]
I'd use a regex:
set s abc0__remote_contr_major_abc__remote_hjk_klo_hcf_uio__apple_b_0_t_boo_dfS
regexp {.*__(.*t)_.*} $s _ t
puts $t ;# => apple_b_0_t
The problem with split $s "__" is that the 2nd argument to split is not a substring: it's a set of characters, so it's just the same as split $s "_"
tcllib has a textutil::split package containing a splitx proc that splits a string on a regular expression
package require textutil::split
namespace import textutil::split::splitx
set last [lindex [splitx $s "__"] end] ;# => apple_b_0_t_boo_dfS
# and then
set wanted [regsub {[^t]*$} $last ""] ;# => apple_b_0_t
Another approach is to find the last place that __ occurs in the string:
set idx [string last "__" $s] ;# => 52
# and then
set last [string range $s $idx+2 end] ;# => apple_b_0_t_boo_dfS
This could also be done:
set s "abc0__remote_contr_major_abc__remote_hjk_klo_hcf_uio__apple_b_0_t_boo_dfs"
set c [string range $s [string first "apple_" $s] [string last "t_" $s]]
puts $c
-> apple_b_0_t
I want to convert a column of a file in to list using Tcl Script. I have a file names "input.dat" with the data in two columns as follows:
7 0
9 9
0 2
2 1
3 4
And I want to convert the first column into a list and I wrote the Tcl Script as follows:
set input [open "input.dat" r]
set data [read $input]
set values [list]
foreach line [split $data \n] {
lappend values [lindex [split $line " "] 0]
}
puts "$values"
close $input
The result shows as: 7 9 0 2 3 {} {}
Now, my question is what is these two extra "{}" and what is the error in my script because of that it's producing two extra "{}" and How can I solve this problem?
Can anybody help me?
Those empty braces indicate empty strings. The file you used most probably had a couple empty lines at the end.
You could avoid this situation by checking a line before lappending the first column to the list of values:
foreach line [split $data \n] {
# if the line is not equal to blank, then lappend it
if {$line ne ""} {
lappend values [lindex [split $line " "] 0]
}
}
You can also remove those empty strings after getting the result list, but it would mean you'll be having two loops. Still can be useful if you cannot help it.
For example, using lsearch to get all the values that are not blank (probably simplest in this situation):
set values [lsearch -all -inline -not $values ""]
Or lmap to achieve the same (a bit more complex IMO but gives more flexibility when you have more complex situations):
set values [lmap n $values {if {$n != ""} {set n}}]
The first {} is caused by the blank line after 3 4.
The second {} is caused by a blank line which indicates end of file.
If the last blank line is removed from the file, then there will be only one {}.
If the loop is then coded in the following way, then there will be no {}.
foreach line [split $data \n] {
if { $line eq "" } { break }
lappend values [lindex [split $line " "] 0]
}
#jerry has a better solution
Unless intermittent empty strings carry some meaning important to your program's task, you may also use a transformation from a Tcl list (with empty-string elements) to a string that prunes empty-string elements (at the ends, and in-between):
concat {*}[split $data "\n"]
i'm converting xls to csv. Since i'm having commas in a single column, i'm getting csv as below:
AMP FAN,Yes,Shichi,PON Seal,,"Brass, Silver"
AMP FAN,Yes,Shichi,PON Seal,,"Platinum, Gel"
If you see double quote is coming for the last column as it has comma inside. Now i'm reading this csv in tcl file and i'm sending to my target system. In target system this value is getting saved with double quotes (means exactly like "Brass, Silver"). But the user doesn't want that double quotes. So i want to set like Brass, Silver . is there any way i can avoid that double quotes. below is the current script i'm using.
while {[gets $fileIn sLine] >= 0} {
#using regex to handle multiple commas in a single column
set matches [regexp -all -inline -- {("[^\"]+"|[^,]*)(?:$|,)} $sLine]
set lsLine {}
foreach {a b} $matches {lappend lsLine $b}
set sType [lindex $lsLine 0]
set sIsOk [lindex $lsLine 1]
set sMaterial [lindex $lsLine 5]
#later i'm setting sMaterial to some attribute
}
Kindly help me.
Note : I will not be able to use csv package as the user don't have that in their environment and i can't add there myself.
You can remove them from the token after getting each element, like this:
while {[gets $fileIn sLine] >= 0} {
#using regex to handle multiple commas in a single column
set matches [regexp -all -inline -- {("[^\"]+"|[^,]*)(?:$|,)} $sLine]
set lsLine {}
foreach {a b} $matches {
# Remove the quotes here
lappend lsLine [string map {\" {}} $b]
}
set sType [lindex $lsLine 0]
set sIsOk [lindex $lsLine 1]
set sMaterial [lindex $lsLine 5]
#later i'm setting sMaterial to some attribute
}
% set input {AMP FAN,Yes,Shichi,PON Seal,,"Brass, Silver"}
AMP FAN,Yes,Shichi,PON Seal,,"Brass, Silver"
% regsub -all \" $input {}
AMP FAN,Yes,Shichi,PON Seal,,Brass, Silver
%
I am new to tcl, trying to learn, need a help for below.
My string looks like in configFileBuf and trying to replace second occurance of ConfENB:local-udp-port>31001" with XYZ, but below regsub cmd i was tried is always replacing with first occurance (37896). Plz help how to replace second occurance with xyz.
set ConfigFileBuf "<ConfENB:virtual-phy>
</ConfENB:local-ip-addr>
<ConfENB:local-udp-port>37896</ConfENB:local-udp-port>
</ConfENB:local-ip-addr>
<ConfENB:local-udp-port>31001</ConfENB:local-udp-port>
</ConfENB:virtual-phy>"
regsub -start 1 "</ConfENB:local-ip-addr>\[ \n\t\]+<ConfENB:local-udp-port>\[0-9 \]+</ConfENB:local-udp-port>" $ConfigFileBuf "XYZ" ConfigFileBuf
puts $ConfigFileBuf
You have to use regexp -indices to find where to start the replacement, and only then regsub. It's not too bad if you put the regular expression in its own variable.
set RE "</ConfENB:local-ip-addr>\[ \n\t\]+<ConfENB:local-udp-port>\[0-9 \]+</ConfENB:local-udp-port>"
set start [lindex [regexp -all -indices -inline $RE $ConfigFileBuf] 1 0]
regsub -start $start RE $ConfigFileBuf "XYZ" ConfigFileBuf
The 1 is the number of submatches in the RE (zero in this case) plus 1. You can compute it with the help of regexp -about, giving this piece of trickiness:
set RE "</ConfENB:local-ip-addr>\[ \n\t\]+<ConfENB:local-udp-port>\[0-9 \]+</ConfENB:local-udp-port>"
set relen [expr {1 + [lindex [regexp -about $RE] 0]}]
set start [lindex [regexp -all -indices -inline $RE $ConfigFileBuf] $relen 0]
regsub -start $start RE $ConfigFileBuf "XYZ" ConfigFileBuf
If your string was well-formed XML I'd suggest something like tDOM to manipulate it. DOM-style manipulation is almost always better than regular expression-based manipulation on XML markup. (I mention this on the off chance that it's actually supposed to be XML and you just quoted it wrong.)
It looks like you're trying to use -start 1 to tell regsub to skip the first match. The starting index is actually a character index, so in this invocation regsub will just skip the first character in the string. You could set -start further into your string, but that's fragile unless you use regexp to calculate where the first match ends.
I think the best solution would be to get a list of indices to matches by invoking regexp with -all -inline -indices, pick out the second index pair using lindex and finally use string replace to perform the substitution, like this:
set pattern {</ConfENB:local-ip-addr>[ \n\t]+<ConfENB:local-udp-port>[0-9 ]+</ConfENB:local-udp-port>}
set matches [regexp -all -inline -indices -- $pattern $ConfigFileBuf]
set match [lindex $matches 1]
set ConfigFileBuf [string replace $ConfigFileBuf {*}$match XYZ]
The variable match contains a pair of indices (start and end, respectively) for the range of characters you want to replace. As string replace expects those indices to be in different arguments you need to expand $match with the {*} prefix. If you have an earlier version of Tcl than 8.5, you need a slight change to the above code:
foreach {start end} $match break
set ConfigFileBuf [string replace $ConfigFileBuf $start $end XYZ]
In passing, note that you can avoid escaping e.g. character sets in a regular expression if you quote it with braces instead of double quotes.
Documentation links: regexp, lindex, string
file1.txt
dut1Loop1Net = [::ip::contract [::ip::prefix 1.1.1.1/24]]/24
My script is
set in [open file1.txt r]
set line [gets $in]
if {[string trim [string range $line1 0 0]] != "#"} {
set devicePort [string trim [lindex $line1 0]]
set mark [expr [string first "=" $line1] + 1]
set val [string trim [string range $line1 $mark end]]
global [set t $devicePort]
set [set t $devicePort] $val
}
close $in
Problem
I am getting output as
% set dut1Loop1Net
[::ip::contract [::ip::prefix 1.1.1.1/24]]/24
Here i am getting the string without evaluating.
I am expecting the output as 1.1.1.0/24. Because TCL does not evaluate code here, it is printing like a string.
I am interesting to know how TCL stores the data and in which form it will retreive the data.
How Tcl stores values.
The short story:
Everything is a string
The long strory
Tcl stores the data in the last used datatype, calculate the string representation only when nessecary, uses copy on write, a simple refcount memory managment.
The answer how you evaluate it is with eval or subst. In your case probably subst.
Edit:
If your config file looks like this:
# This is a comment
variable = value
othervar = [doStuff]
you can use some tricks to get Tcl parsing it for you:
rename ::unknown ::_confp_unknown_orig
proc unknown args {
if {[llength $args] == 3 && [lindex $args 1] eq "="} {
# varname = value
uplevel 1 [list set [lindex $args 0] [lindex $args 2]
return [lindex $args 2]
}
# otherwise fallback to the original unknown
uplevel 1 [linsert $args 0 ::_confp_unknown_orig]
# if you are on 8.6, replace the line above with
# tailcall ::_confp_unknown_orig {*}$args
}
# Now just source the file:
source file1.txt
# cleanup - if you like
rename ::unknown {}
rename ::_confp_unknown_orig ::unknown
An other way to do that is to use a safe interp, but in this case using your main interp looks fine.
The problem is that the code you store inside val is never executed.
You access it using $val, but this way you get the code itself, and not the result of its execution.
To solve it, you must be sure [::ip::contract [::ip::prefix 1.1.1.1/24]]/24 is executed, and you can do that by replacing this line
set val [string trim [string range $line1 $mark end]]
with this one
eval "set val [string trim [string range $line1 $mark end]]"
Why? Here's my simple explaination:
The parser sees the "..." part, so it performs substitutions inside it
The first substitution is the execution of the string range $line1 $mark end command
The second substitution is the execution of the string trim ... command
So, when substitutions are complete and the eval command is ready to run, its like your line has become
eval {set val [::ip::contract [::ip::prefix 1.1.1.1/24]]/24}
Now the eval command is executed, it calls recursively the interpreter, so the string set val [::ip::contract [::ip::prefix 1.1.1.1/24]]/24 goes to another substitution phase, which finally runs what you want and puts the string 1.1.1/24 into the variable val.
I hope this helps.