I encountered this issue on both Solaris and Linux, with tcl version 8.3/8.4
please see the following code:
#!/usr/bin/tclsh
set pattern "this is * and *"
set str "this is tcl and c++"
switch -glob $str {
$pattern {
puts "matched pattern"
}
"this is * and *" {
puts "matched plain text"
}
default {
puts "matched none"
}
}
and the result is "matched plain text".
I though it should have matched the $pattern... is this an incorrect usage of switch, or I am not giving correct pattern for -glob option?
please someone give some idea and it is better if you can tell how to modify the code to make it run with switch and variable.
Thanks!
XM
I believe you're misunderstanding how Tcl parsing/substitution works. Specifically, the glob command is getting the arguments:
1: -glob
2: $str
3: {
$pattern {
puts "matched pattern"
}
"this is * and *" {
puts "matched plain text"
}
default {
puts "matched none"
}
}
The {...} construct "groups" the things inside it into a single, un-substituted (verbatim) piece of data. As such, the third argument to switch contains "$pattern", not the result of replacing $pattern with the value of the pattern variable.
If you need substitution, you need to avoid using curly braces (I'm simplifying). So, if you really want to substitute the value for the pattern in, the easiest way to do it is to use the other form of switch (which passes in each pattern/code block as a separate argument):
switch -glob $str $pattern {
puts "matched pattern"
} "this is * and *" {
puts "matched plain text"
} default {
puts "matched none"
}
As a note, it's almost always a good idea to use the -- (no more flags) flag with switch when you're using a variable substitution for the string to match. That avoids the problem where your $str contains something starting with a -
switch -glob -- $str $pattern {
puts "matched pattern"
} "this is * and *" {
puts "matched plain text"
} default {
puts "matched none"
}
Related
I am trying to find multiple string patterns in a string in TCL. I cannot get the correct and optimized way to do that.
I have tried some code and it is not working
I have to find -h ,-he,-hel ,-help in the string -help
set args "-help"
set res1 [string first "-h" $args]
set res2 [ string first -he $args]
set res3 [string first -hel $args]
set res4 [string first "-help" $args"]
if { $res1 == -1 || $res2 || $res3 || $res4 } {
puts "\n string not found"
} else {
puts "\n string found"
}
how to use regexp here I am not sure , so need some inputs.
The expected output is
This is a case where using regexp is easier. (Asking if a string is a prefix of -help is a separate problem.) The trick here is to use ? and (…) (or rather (?:…) which is the non-capturing version) in the RE and you must use the -- option because the RE begins with a -:
if {[regexp -- {-h(?:e(?:lp?)?)?} $string]} {
puts "Found the string"
} else {
puts "Did not find the string"
}
If you want to know what string you actually found, add in a variable to pick up the overall match:
if {[regexp -- {-h(?:e(?:lp?)?)?} $string matched]} {
puts "Found the string '$matched'"
} else {
puts "Did not find the string"
}
If you instead want the indices where it matched, you need an extra option:
if {[regexp -indices -- {-h(?:e(?:lp?)?)?} $string match]} {
puts "Found the string at $match"
} else {
puts "Did not find the string"
}
If you were instead interested in whether the string was a prefix of -help, you instead should do:
if {[string equal -length [string length $string] $string "-help"]} {
puts "Found the string"
} else {
puts "Did not find the string"
}
Many uses of this sort of thing are actually doing command line parsing. In that case, the tcl::prefix command is very useful. For example, tcl::prefix match finds the entry in a list of options that a string is a unique prefix of and generates an error message when things are ambiguous or simply don't match; the result can be switched on easily:
set MY_OPTIONS {
-help
-someOtherOpt
}
switch [tcl::prefix match $MY_OPTIONS $string] {
-help {
puts "I have -help"
}
-someOtherOpt {
puts "I have -someOtherOpt"
}
}
I had an expect script that was working; it contained the following lines:
expect "\[Y\]\> "
send "n\r"
The OS I'm interacting with changed, I now have a mixed environment of boxes. Some now have "[Y]>", some have "[1]>" at the point I'm trying to deal with.
I tried changing the code to:
expect {
"\[Y\]\> " { send "n\r";exp_continue }
"\[1\]\> " { send "2\r";exp_continue }
}
however, running in debug I see:
"Choose the password option:
1. Mask passwords
2. Plain passphrases
[1]>
expect: does "s...ses\r\n[1]> " (spawn_id exp5) match glob pattern "[Y]> "? no
"[1]> "? no
expect: timed out"
I don't understand why the revised code is not working, for either "[Y]> " or "[1]> ", when "[Y]> " was being matched before the 'else' statement was introduced.
The problem is with escaping the square brackets which is special to Tcl.
# Using braces for pattern
expect {
{\[Y]>} {puts Y}
{\[1]>} { puts 1}
}
# or
# Using double quotes
expect {
"\\\[Y]>" {puts Y}
"\\\[1]>" { puts 1}
}
We don't have to escape the closing bracket ] and the symbol >
According to the TCL man page for "string match" I should be able to do a character class. A simple example:
set rev revA
if { [string match "revA*" $rev } {
puts "revA"
} elseif {[string match "revB" $rev]} {
puts "revB"
} elseif {[string match "rev[CD]" $rev]} {
puts "revC or D"
} else {
puts "none"
}
Without the "rev[CD]" section it works fine but as shown above I get:
% tclsh tmp.tcl
missing close-bracket
What am I missing?
The real error is that you are actually missing a close bracket in the first if
if { [string match "revA*" $rev } {
# .............................^
Don't forget that [brackets] also signify Tcl's command substitution, and command substitution happens within double quotes. You need to either escape the open bracket or use different quotes:
} elseif {[string match "rev\[CD]" $rev]} {
} elseif {[string match {rev[CD]} $rev]} {
However, it's more efficient in this case, instead of cascading if-elseifs, to use switch
switch -glob -- $rev {
"revA*" {puts "revA"}
"revB" {puts "revB"}
{rev[CD]} {puts "revC or D"}
default {puts "none"}
}
I want to find a pattern in a file, but the pattern can have several forms.
Here is the code :
while {[gets $thefile line] >= 0} {
for {set nb_table 1} {$nb_table<$count_table} {incr nb_table} {
if { [regexp {pattern_$nb_table} $line] } {
puts "I found one !"
}
}
}
the var $count_table is known, catched before on a on a other procedure.
If i do a puts of pattern_$nb_table in the for loop i got the name of all tables and that's good, but I never have I found one! printed out (sure i want to be another process but it is not the subject). Why I never go in the if? My file contains the pattern : pattern_1 pattern_2 .....
The problem is that the variable is not being substituted into the regular expression (the {…} disable all immediate substitutions). This is a situation where you'd use (putting the variable name in braces just for clarity, and putting the pattern in double quotes for highlighting only):
if {[regexp "pattern_${nb_table}" $line]} { ... }
Except that if I was looking for a string that simple, I'd try to use string first or string match:
if {[string first "pattern_${nb_table}" $line] >= 0} { ... }
if {[string match "*pattern_${nb_table}*" $line]} { ... }
Both of these are faster than regular expression matching, provided you're doing something simple. If the rest of the real pattern is a regular expression, only regexp will do. Of course.
How would I get the switch statement to math the closing bracket? I tried both "[][]" and "[[\]]"
set x "]"
switch -glob $x {
"[[\]]" {
puts "MATCH ]"
}
}
you can use:
set x "]";
switch -glob $x {
\] {
puts "MATCH ]"
}
}
or to match more than the bracket:
set x "foo]bar";
switch -glob $x {
*\]* {
puts "MATCH ]"
}
}
When you want to match ] as part of a group of characters in switch, you've only got a few options:
Use -regexp matching instead of -glob. It's more complex, requires a different pattern, but it's also definitely more flexible (especially when it comes to things like this).
switch -regexp $x {
^[][(){}]$ {
puts "Matched!"
}
}
Use multiple clauses with body sharing:
switch -glob $x {
{\[} - {\]} - [(){}] {
puts "Matched!"
}
}
What you can't do is put a ] in a glob match set except as a non-initial part of a range (the glob matcher is pretty dumb, but fast). There isn't any suitable range that would match exactly what you're after here, so a single glob won't work.