Variable substitution in a list in Tcl - list

I am trying to substitute a variable in a list:
% set a 1
1
% set b {1 $a}
1 $a
%
% set a 1
1
% set b [list 1 $a]
1 1
When I use the curly braces {}, to define the list the variable 'a' doesn't get substituted with its value but in case of using list it does.
How do I substitute a variable's value when using a list defined with {}?

% set a 1
1
% subst {1 $a}
1 1
% set b [subst {1 $a}]
1 1
But most Tcl:ers actually prefer to use list when creating a list value with substitutions, like in your second try:
% set b [list 1 $a]
One reason for this is that subst won't preserve list structure in the value:
% set a {a b}
a b
% set b [subst {1 $a}]
1 a b
% set b [list 1 $a]
1 {a b}
Another possibility is to use double quotes instead of braces; this also won't preserve list structure.
% set b "1 $a"
1 a b
Documentation:
list,
set,
subst,
Summary of Tcl language syntax

How do I substitute a variable's value when using a list defined with {}?
In short, you don't.
What {} really does is define an unsubstituted string. It might be possible to interpret that as a list, but it could be other things as well, such as script. It's part of the fundamental nature of Tcl that it is all of these things.
If you want the variable substituted, you use a different sort of quoting or you run the result through subst (which is defined to work over strings). The “quoting” most commonly used for lists these days is the list command, as that preserves element boundaries correctly.
I suppose it is possible to do things with various string operations to make a command that could post-process the string as you want, but it would be slow and complicated:
proc substList {string} {
# Add backslashes in front of metacharacters
set safer [string map {\\ \\\\ [ \\[ ] \\]} $string]
# Convert variable mentions to our “safe” version
regsub -all {\$\w+} $safer {[list &]} tmpl
# Do the substitutions in the caller's context
tailcall subst $tmpl
}
Giving a quick test:
% set a "1 2 3"
1 2 3
% set b {x $a}
x $a
% substList $b
x {1 2 3}
Appears to work in this simple case, though I wouldn't want to warrant that it's actually right. (After all, it's using string map and regexp and subst; what could possibly go wrong?) Just use list properly, OK?

Related

Tcl multiple numerical starts within same string

How do I do this in tcl
Say I have a list $list1 containing 5 such entries
blah_1_X11Y2_R0/Isi_bl_X8Y0/wrap_bl_X0Y0_R0/Isine_MY/core
blah_1_X13Y2_R0/Isi_bl_X5Y0/wrap_bl_X0Y0_R0/Isine_MY/core
blah_1_X11Y2_R0/Isi_br_X7Y0/wrap_br_X1Y0_R0/Isine_R0/core
blah_1_X11Y2_R0/Isi_bl_X17Y0/wrap_bl_X0Y0_R0/Isine_MY/core
blah_1_X11Y2_R0/Isi_br_X15Y0/wrap_br_X1Y0_R0/Isine_R0/core
I want to sort them numerically and output to be like this
blah_1_X11Y2_R0/Isi_br_**X7**Y0/wrap_br_X1Y0_R0/Isine_R0/core
blah_1_X11Y2_R0/Isi_bl_**X8**Y0/wrap_bl_X0Y0_R0/Isine_MY/core
blah_1_X11Y2_R0/Isi_br_**X15**Y0/wrap_br_X1Y0_R0/Isine_R0/core
blah_1_X11Y2_R0/Isi_bl_**X17**Y0/wrap_bl_X0Y0_R0/Isine_MY/core
blah_1_**X13**Y2_R0/Isi_bl_**X5**Y0/wrap_bl_X0Y0_R0/Isine_MY/core
Thanks
This sounds almost like a job for lsort -dictionary, yet we need a bit of work to extract the numeric parts because we don't seem to want to sort on the non-numeric parts. (I'm assuming you've got your data in a list variable called data.)
# Extract the parts we want to sort on
set nums [lmap item $data {
# The collation key is a list of all digit sequences in the input value
regexp -all -inline {\d+} $item
}]
# Sort and remap back onto the original data
set sorted_data [lmap idx [lsort -dictionary -indices $nums] {
lindex $data $idx
}]
The -indices option is very useful for when you have a collation key (something you've extracted from the data that you want to sort on) as it means that you don't need to zip that into the original data to do the sort. And lmap is just so useful for these sorts of things.
The collation key extracted from:
blah_1_X11Y2_R0/Isi_bl_X8Y0/wrap_bl_X0Y0_R0/Isine_MY/core
is:
1 11 2 0 8 0 0 0 0
And I think your data gets sorted as:
blah_1_X11Y2_R0/Isi_br_X7Y0/wrap_br_X1Y0_R0/Isine_R0/core
blah_1_X11Y2_R0/Isi_bl_X8Y0/wrap_bl_X0Y0_R0/Isine_MY/core
blah_1_X11Y2_R0/Isi_br_X15Y0/wrap_br_X1Y0_R0/Isine_R0/core
blah_1_X11Y2_R0/Isi_bl_X17Y0/wrap_bl_X0Y0_R0/Isine_MY/core
blah_1_X13Y2_R0/Isi_bl_X5Y0/wrap_bl_X0Y0_R0/Isine_MY/core
If that's not quite correct, a more complex method of picking out the collation key should do the trick.

Why does special characters in my variable disappear on doing an lindex in TCL?

I have a list in my application that i work on.. Its basically like this:
$item = {text1 text2 text3}
Then I pick up the first member in the list with:
lindex $item 0
On doing this text1 which used to be (say) abcdef\12345 becomes abcdef12345.
But its very important for me to not lose this \ . Why is it disappearing. THere are other characters like - and > which don't disappear. Please note that I cannot escape the \ in the text beforehand. If there's anything I can do before operating on the $item with lindex, please suggest.
The problem is that \ is a Tcl list metasyntax character, unlike -, > or any alphanumeric. You need to convert your string into a proper Tcl list before using lindex (or any other list-consuming operation) on it. To do that, you need to understand exactly what you mean by “words” in your input data. If your input data is a sequences of non-whitespace characters separated by single whitespace characters, you can use split to do the conversion to a list:
set properList [split $item]
# Now we can use it...
set theFirstWord [lindex $properList 0]
If you've got a different separator, split takes an optional extra character to say what to split by. For example, to split by colons (:) you do:
set properList [split $item ":"]
However, if you have other sorts of splitting rules, this doesn't work so well. For example, if you can split by multiple whitespace characters, it's actually better to use regexp (with the -all -inline options) to do the word-identification:
# Strictly, this *chooses* all sequences of one or more non-whitespace characters
set properList [regexp -all -inline {\S+} $item]
You can also do splitting by multi-character sequences, though in that case it is most easily done by mapping (with string map) the multi-character sequence to a single rare character first. Unicode means that there are lots of such characters to pick…
# NUL, \u0000, is a great character to pick for text, and terrible for binary data
# For binary data, choose something beyond \u00ff
set properList [split [string map {"BOUNDARY" "\u0000"} $item] "\u0000"]
Even more complex options are possible, but that's when you use splitx from Tcllib.
package require textutil::split
# Regular expression to describe the separator; very sophisticated approach
set properList [textutil::split::splitx $item {SPL+I*T}]
In tcl Lists can be created in several ways:
by setting a variable to be a list of values
set lst {{item 1} {item 2} {item 3}}
with the split command
set lst [split "item 1.item 2.item 3" "."]
with the list command.
set lst [list "item 1" "item 2" "item 3"]
And an individual list member can be accessed with the lindex command.
set x "a b c"
puts "Item 2 of the list {$x} is: [lindex $x 2]\n"
This will give output:
Item 2 of the list {a b c} is: c
And With respect to the question asked
You need to define the variable like this abcdef\\12345
In order to make this clear try to run the following command.
puts "\nI gave $100.00 to my daughter."
and
puts "\nI gave \$100.00 to my daughter."
The second one will give you the proper result.
If you don't have the option to change the text, try to save the text in curly braces, as mentioned in the first example.
set x {abcd\12345}
puts "A simple substitution: $x\n"
Output:
A simple substitution: abcd\12345
set y [set x {abcdef\12345}]
And check for this output:
puts "Remember that set returns the new value of the variable: X: $x Y: $y\n"
Output:
Remember that set returns the new value of the variable: X: abcdef\12345 Y: abcdef\12345

How to use regexp to grab elements of a tcl string

I have extracted some data from a tabular column using "lsearch" and now have a TCL variable like this
{ 1 no8 MASTER (UP-DOWN) ABCD 1456 /clown F right_left_123 /local/opt/data WXYZ (M5,N6) }
How can I now use "regexp" to grab each of these values into separate variables? I guess I will have to filter by space, but the blank space between these values are variable.Also, I am a "regexp" newbie.
I tried using "lindex" but looks like the entire element is in index 0. Please let me know what is the easiest way.
lsearch has probably returned a list containing this 1 element. If you want to now get the elements inside this element, use a second index, to go 1 level deeper:
# suppose the list in in the variable $l
puts [lindex $l 0 0]
# => 1
puts [lindex $l 0 1]
# => no8

lsearch does not match elements that require curly-braces (Tcl 8.4)

I'm dealing with a big number of signals. I've been able to store them into a list, but since their name have brackets the signals are store in a list. Latter on, using regexp, I analyze some output produced and, if there's a match, I needed to set a flag.
In this following example I show the element added to the list and, later one, I try to check if the same element is inside of the list using lsearch
set mylist [list]
set element {aux[1]}
lappend mylist $element
puts "mylist: $mylist \nelement: $element\n\[list element\]: [list $element]"
The result of this puts is:
mylist: {aux[1]}
element: aux[1]
[list element]: {aux[1]}
Since my element is stored as {a[1]}, I've not found a way to make lsearch to return a match
set result [lsearch $mylist $element]
set result2 [lsearch $mylist [list $element]]
puts $result
puts $result2
Both results return '-1'.
I've seen solutions, but none of them using Tcl 8.4; And I need to use it due to backwards compatibility.
Use the -exact matching style. The default style is -glob, which means that the substring [1] matches a single 1.
lsearch -exact $mylist $element
# => 0
Documentation: lsearch

splitting a formula and again regenerating and reevaluateing formula

I m splitting a formula string with "*/+-()" as my pattern (for eg. a*b+c is string) and I m getting a list in the output as (a b c) where a,b,c are variables and contain some values like 5,10,15.
What I need is: I should be able to directly substitute values in the variables and evaluate the expression.
The formula is taken from the user and changes time to time. so if the user enters (a/b), something should automatically replace it with real values (5/10) and then return the result 0.5.
The formula is formed from limited number of variables (for eg. a,b,c) and it can use +,-,*,/,(,) as operators.
The problem is that after splitting the variables, i m not able to replace them with their values or evaluate the equation. Please help me to do this task in as short expression as possible. thanks in advance.
It is not at all that complicated:
First, replace all variables with with a Tcl variable (prepped a $).
You have to be careful not replace sin(a) with $sin($a) or similar.
regsub -all -inline {[a-z]+(?![a-z\(])} $input
Example:
set input {a*b+c+sin(d)}
regsub -all -inline {[a-z]+(?![a-z\(])} $input
would yield $a+$b*$c+sin($c), which can be passed to expr.
If you need the variable names, just use regexp with this expression.
If you know the names of the variables and none of them are prefixes of anything else you use, you can easily transform the expression like this:
set a 1; set b 2; set c 3
set e "a*b+c"
set value [expr [string map {a $a b $b c $c} $e]]
puts "$e = $value"
Note: no braces around the expression on the third line. This is when you want to avoid safety like that because you are doing runtime generation of the expression.
That mapping can be generated automatically:
set a 1; set b 2; set c 3
set e "a*b+c"
set vars {a b c}
set value [expr [string map [regsub -all {\w+} "& $&"] $e]]
puts "$e = $value"
However, if you've got prefixes and other things like that, you need a more complex transform:
# Omitting the variable setup and print at the end...
proc replIfRight {vars word} {
if {$word in $vars} {return \$$word} else {return $word}
}
set value [expr [subst [regsub -all {\w+} [string map {[ \[ $ \$ \\ \\\\} $e] {[replIfRight $vars &]}]]]
You're absolutely right to not expect to come up with such a horrible thing yourself!