TCL regsub replace last / in file name - tcl

To replace the last /in a file name with : I tried following code.
set x a/b/c
regsub {.*(\/)\S+$} $x {:}
The undesired result is :.
The desired result is a/b:c
Examples:
a -> a
a/b -> a:b
aa/b -> aa:b
aa/bb/.../dd/e -> aa/bb/.../dd:e
Feedback is appreciated.

With a regular expression, you can say
set new [regsub {/([^/]+)$} $x {:\1}] ;# ==> a/b:c
foreach d {a a/b aa/b aa/bb/.../dd/e} {puts "$d => [regsub {/([^/]+)$} $d {:\1}]"}
# a => a
# a/b => a:b
# aa/b => aa:b
# aa/bb/.../dd/e => aa/bb/.../dd:e
Or, use the file command
set new [file dirname $x]:[file tail $x] ;# ==> a/b:c
The problem with the 2nd option is if the string does not contain a slash, you get a => .:a so you need to do something like this:
foreach d {a a/b aa/b aa/bb/.../dd/e} {
set new [expr {[string first / $d] == -1 ? $d : "[file dirname $d]:[file tail $d]"}]
puts "$d -> $new"
}

Related

How to split the string and save the last word in Tcl

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

TCL script to extract the elements from a file in a directory and make a list

I am having one problem as stated below
Problem Description
I am having some "sv" extension files , I am using "glob" to extract the matching files , Now in these matching files , I need to split them and extract the elements and create different lists.
For example
set files [glob -type f modified_rtl_files/*/*{wrapper_hsm}*]
This command gives me these two files :
modified_rtl_files/mbist_wrapper/mbist_wpper_hsm_pkram.sv modified_rtl_files/mbi_wrapper/mbist_wrapper_hsm_sysram.sv
Now I am extracting the filename with the below command
foreach element $files {
set c [split [ file tail [ file rootname $element ] ] "_" ]
echo $c
}
This is giving me
pkram
sysram
But I need to set them to two independent list
$mem1
$mem2
$mem1 should be pkram
$mem2 should be sysram
Whenever I am trying to create something like this , both the elements are getting printed
foreach element $files {
set c [split [ file tail [ file rootname $element ] ] "_" ]
set d [ lindex $c 3]
set mem1 [ lindex $d 0 ]
puts $mem1
}
It is printing both
pkram
sysram
I want independent variables .
Try adding the values you want into an array instead of numbered variables:
set files {modified_rtl_files/mbist_wrapper/mbist_wpper_hsm_pkram.sv modified_rtl_files/mbi_wrapper/mbist_wrapper_hsm_sysram.sv}
set mem {}
foreach f $files {
lappend mem [file rootname [lindex [split $f _] end]]
}
puts [lindex $mem 0] ;# => pkram
puts [lindex $mem 1] ;# => sysram
Or use the lmap command to make it more concise:
set mem [lmap f $files {file rootname [lindex [split $f _] end]}]
You can do generated names with set:
foreach element [lsort $files] {
set mem[incr i] [lindex [split [file tail [file rootname $element]] "_"] 3]
puts [set mem$i]; # <<< How to *READ* a variable with a generated name
}
(Always sort the output of glob if you care about the order at all; it's not guaranteed to come in any order at all or with any consistency).
BUT DON'T DO THIS!
Generated variable names are a pain to work with precisely because they can't be used with the $ language syntax. You're much better using (associative) arrays because those do work with that:
foreach element [lsort $files] {
set mem([incr i]) [lindex [split [file tail [file rootname $element]] "_"] 3]
puts $mem($i)
}
The values will not be in mem1 and mem2, but rather mem(1) and mem(2).

TCL script to print range of lines and create the variables

Hi I am having a code as stated below
module abcd( a , b , c ,da , fa, na , ta , ma , ra ,
ta, la , pa );
input a , b, da ,fa , na , ta , ma;
output c , ra ,ta ,
la ,pa ;
wire a , b , da , fa ,na ,
ta , ma;
// MBIST Structures
mbist_hsm_p::mbist_out_hsm_t mbist_out_hsm;
mbist_hsm_p::mbist_in_hsm_t mbist_in_hsm;
// HMS
kkkks ;
jsskks;
endmodule
Need to take the range between "MBIST Structures " and "//" and the take the first line as a input variable and second line as a output variable.
For example , I am trying below stated code
proc findrange {data start {stop ;}} {
# Find the starting pattern
set x1 [string first $start $data]
if {$x1 < 0} {
# Pattern not found
return
}
# Skip the pattern
incr x1 [string length $start]
# Find the ending pattern after the starting position
set x2 [string first $stop $data $x1]
if {$x2 < 0} {
# Return the remainder of the data when no ending pattern is found
return [string range $data $x1 end]
} else {
# Return the text between the starting and ending patterns
return [string range $data $x1 [expr {$x2 - 1}]]
}
}
set chan [open "mode.v"]
set code [read $chan]
close $chan
set var4 [ findrange $code "MBIST Structures" \/\/]
echo $var4 is printing these variables
mbist_hsm_p::mbist_out_hsm_t mbist_out_hsm;
mbist_hsm_p::mbist_in_hsm_t mbist_in_hsm;
I want to have two lists
$input should be "mbist_hsm_p::mbist_out_hsm_t mbist_out_hsm;"
$output should be "mbist_hsm_p::mbist_in_hsm_t mbist_in_hsm;"
How to create these variables from the var4 variable
When I am trying out to print out the $var4 variable , it is printing 4 independent variables
foreach p $var4 {
echo $p
}
mbist_hsm_p::mbist_out_hsm_t
mbist_out_hsm;
mbist_hsm_p::mbist_in_hsm_t
mbist_in_hsm;
Rather it should be " mbist_hsm_p::mbist_out_hsm_t mbist_out_hsm;"
and other one should be "mbist_hsm_p::mbist_in_hsm_t mbist_in_hsm;"
Two lists I am looking for
$input and $output
With a short input file like this, it is much easier to read the whole file into a variable. And for the described task I think string first is a better choice than string match.
So this is how I would do it:
proc findrange {data start {stop ;}} {
# Find the starting pattern
set x1 [string first $start $data]
if {$x1 < 0} {
# Pattern not found
return
}
# Skip the pattern
incr x1 [string length $start]
# Find the ending pattern after the starting position
set x2 [string first $stop $data $x1]
if {$x2 < 0} {
# Return the remainder of the data when no ending pattern is found
return [string range $data $x1 end]
} else {
# Return the text between the starting and ending patterns
return [string range $data $x1 [expr {$x2 - 1}]]
}
}
set chan [open "mod1.v"]
set code [read $chan]
close $chan
set out [open "output.file.txt" "w"]
puts $out [findrange $code input]
puts $out [findrange $code output]
close $out
There is some change in the white space between your input and the desired
output you specified. But you didn't indicate the rules for that transformation and they are not obvious. So, I am ignoring that for the moment.

how to split a file to list of lists TCL

I'm coding TCL and I would like to split a file into two lists of lists,
the file contain:
(1,2) (3,4) (5,6)
(7,8) (9,10) (11,12)
and I would like to get two list
one for each line, that contain lists that each one contain to two number
for example:
puts $list1 #-> {1 2} {3 4} {5 6}
puts [lindex $list1 0] #-> 1 2
puts [lindex $list2 2] #-> 11 12
I tried to use regexp and split but no success
The idea of using regexp is good, but you'll need to do some post-processing on its output.
# This is what you'd read from a file
set inputdata "(1,2) (3,4) (5,6)\n(7,8) (9,10) (11,12)\n"
foreach line [split $inputdata "\n"] {
# Skip empty lines.
# (I often put a comment format in my data files too; this is where I'd handle it.)
if {$line eq ""} continue
# Parse the line.
set bits [regexp -all -inline {\(\s*(\d+)\s*,\s*(\d+)\s*\)} $line]
# Example results of regexp:
# (1,2) 1 2 (3,4) 3 4 (5,6) 5 6
# Post-process to build the lists you really want
set list([incr idx]) [lmap {- a b} $bits {list $a $b}]
}
Note that this is building up an array; long experience says that calling variables list1, list2, …, when you're building them in a loop is a bad idea, and that an array should be used, effectively giving variables like list(1), list(2), …, as that yields a much lower bug rate.
An alternate approach is to use a simpler regexp and then have scan parse the results. This can be more effective when the numbers aren't just digit strings.
foreach line [split $inputdata "\n"] {
if {$line eq ""} continue
set bits [regexp -all -inline {\([^()]+\)} $line]
set list([incr idx]) [lmap substr $bits {scan $substr "(%d,%d)"}]
}
If you're not using Tcl 8.6, you won't have lmap yet. In that case you'd do something like this instead:
foreach line [split $inputdata "\n"] {
if {$line eq ""} continue
set bits [regexp -all -inline {\(\s*(\d+)\s*,\s*(\d+)\s*\)} $line]
set list([incr idx]) {}
foreach {- a b} $bits {
lappend list($idx) [list $a b]
}
}
foreach line [split $inputdata "\n"] {
if {$line eq ""} continue
set bits [regexp -all -inline {\([^()]+\)} $line]
set list([incr idx]) {}
foreach substr $bits {
lappend list($idx) [scan $substr "(%d,%d)"]
# In *very* old Tcl you'd need this:
# scan $substr "(%d,%d)" a b
# lappend list($idx) [list $a $b]
}
}
You have an answer already, but it can actually be done a little bit simpler (or at least without regexp, which is usually a good thing).
Like Donal, I'll assume this to be the text read from a file:
set lines "(1,2) (3,4) (5,6)\n(7,8) (9,10) (11,12)\n"
Clean it up a bit, removing the parentheses and any white space before and after the data:
% set lines [string map {( {} ) {}} [string trim $lines]]
1,2 3,4 5,6
7,8 9,10 11,12
One way to do it with good old-fashioned Tcl, resulting in a cluster of variables named lineN, where N is an integer 1, 2, 3...:
set idx 0
foreach lin [split $lines \n] {
set res {}
foreach li [split $lin] {
lappend res [split $li ,]
}
set line[incr idx] $res
}
A doubly iterative structure like this (a number of lines, each having a number of pairs of numbers separated by a single comma) is easy to process using one foreach within the other. The variable res is used for storing result lines as they are assembled. At the innermost level, the pairs are split and list-appended to the result. For each completed line, a variable is created to store the result: its name consists of the string "line" and an increasing index.
As Donal says, it's not a good idea to use clusters of variables. It's much better to collect them into an array (same code, except for how the result variable is named):
set idx 0
foreach lin [split $lines \n] {
set res {}
foreach li [split $lin] {
lappend res [split $li ,]
}
set line([incr idx]) $res
}
If you have the results in an array, you can use the parray utility command to list them in one fell swoop:
% parray line
line(1) = {1 2} {3 4} {5 6}
line(2) = {7 8} {9 10} {11 12}
(Note that this is printed output, not a function return value.)
You can get whole lines from this result:
% set line(1)
{1 2} {3 4} {5 6}
Or you can access pairs:
% lindex $line(1) 0
1 2
% lindex $line(2) 2
11 12
If you have the lmap command (or the replacement linked to below), you can simplify the solution somewhat (you don't need the res variable):
set idx 0
foreach lin [split $lines \n] {
set line([incr idx]) [lmap li [split $lin] {
split $li ,
}]
}
Still simpler is to let the result be a nested list:
set lineList [lmap lin [split $lines \n] {
lmap li [split $lin] {
split $li ,
}
}]
You can access parts of the result similar to above:
% lindex $lineList 0
{1 2} {3 4} {5 6}
% lindex $lineList 0 0
1 2
% lindex $lineList 1 2
11 12
Documentation:
array,
foreach,
incr,
lappend,
lindex,
lmap (for Tcl 8.5),
lmap,
parray,
set,
split,
string
The code works for windows :
TCL file code is :
proc captureImage {} {
#open the image config file.
set configFile [open "C:/main/image_config.txt" r]
#To retrive the values from the config file.
while {![eof $configFile]} {
set part [split [gets $configFile] "="]
set props([string trimright [lindex $part 0]]) [string trimleft [lindex $part 1]]
}
close $configFile
set time [clock format [clock seconds] -format %Y%m%d_%H%M%S]
set date [clock format [clock seconds] -format %Y%m%d]
#create the folder with the current date
set folderPath $props(folderPath)
append folderDate $folderPath "" $date "/"
set FolderCreation [file mkdir $folderDate]
while {0} {
if { [file exists $date] == 1} {
}
break
}
#camera selection to capture image.
set camera "video"
append cctv $camera "=" $props(cctv)
#set the image resolution (XxY).
set resolutionX $props(resolutionX)
set resolutionY $props(resolutionY)
append resolution $resolutionX "x" $resolutionY
#set the name to the save image
set imagePrefix $props(imagePrefix)
set imageFormat $props(imageFormat)
append filename $folderDate "" $imagePrefix "_" $time "." $imageFormat
set logPrefix "Image_log"
append logFile $folderDate "" $logPrefix "" $date ".txt"
#ffmpeg command to capture image in background
exec ffmpeg -f dshow -benchmark -i $cctv -s $resolution $filename >& $logFile &
after 3000
}
}
captureImage
thext file code is :
cctv=Integrated Webcam
resolutionX=1920
resolutionY=1080
imagePrefix=ImageCapture
imageFormat=jpg
folderPath=c:/test/
//camera=video=Integrated Webcam,Logitech HD Webcam C525
This code works for me me accept the code from text file were list of parameters are passed.

Redirecting the "parray" output to a file in tcl

I have an array in tcl.
For example:
set a(1) "First element"
set a(2) "second element"
parray a
parray a displays output as
a(1) = "First element"
a(2) = "second element"
Is it possible to redirect the parray output to a file?
The parray command can't be redirected. It's a simple-minded procedure that is too stupid to be redirected. But it's source code isn't very long; in fact, it's short enough that I'll just paste it here (it's under the Tcl license):
proc parray {a {pattern *}} {
upvar 1 $a array
if {![array exists array]} {
return -code error "\"$a\" isn't an array"
}
set maxl 0
set names [lsort [array names array $pattern]]
foreach name $names {
if {[string length $name] > $maxl} {
set maxl [string length $name]
}
}
set maxl [expr {$maxl + [string length $a] + 2}]
foreach name $names {
set nameString [format %s(%s) $a $name]
puts stdout [format "%-*s = %s" $maxl $nameString $array($name)]
}
}
Redirecting it (hint: change the stdout for something obtained from open … a, and don't forget to close it afterwards) should be a simple exercise.
This builds on the answers by Dinesh and Donal Fellows: You could adapt the code of parray automatically, like this:
auto_load parray
proc printArray {a {pattern *} {channel stdout}} \
[string map {stdout $channel} [info body parray]]
This gives you a new proc printArray with an optional channel argument.