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"}
}
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'm trying to do a If not on a string match with Tcl. However, when I expect it not to match, it seems to be matching because when it shouldn't match it continues to "I don't want it to do this". Hope this makes sense. Inside the log.text file, it should contain, "This is a String."
set var1 "String"
set file [open "log.text" r]
while {[gets $file data] != -1} {
if {![string match *[string toupper $var1]* [string toupper $data]]} {
*I don't want it to do this
}
}
Your code appears to work fine:
$ cat log.text
This is a String
this line does not match
$ tclsh <<'END'
set var1 "String"
set file [open "log.text" r]
while {[gets $file data] != -1} {
if {![string match -nocase *$var1* $data]} {
puts "$data: does not match $var1"
}
}
END
outputs
this line does not match: does not match String
Ah, now you have clearly stated what you want: does the string exist in the file, yes or no. Here are some ways to accomplish that:
read the entire file, and string match against that.
set file [open log.text r]
set contents [read -nonewline $file]
close $file
set pattern_exists [string match -nocase *$var1* $contents]
if {$pattern_exists} {puts "$var1 found in file"}
read the file line-by-line until the pattern is found
set pattern_exists false
set file [open log.text r]
while {[gets $file line] != -1} {
if {[string match -nocase *$var1* $line]} {
set pattern_exists true
break
}
}
close $file
if {$pattern_exists} {puts "$var1 found in file"}
call out to grep to do the heavy lifting: grep exits with non-zero status when the pattern is not found, and exec thinks a non-zero exit status is an exception (see https://tcl.tk/man/tcl8.6/TclCmd/exec.htm#M27)
try {
exec grep -qi $var1 log.text
set pattern_exists true
} on error {e} {
set pattern_exists false
}
if {$pattern_exists} {puts "$var1 found in file"}
The code as you wrote it works… but I'm guessing it is a proxy for something else. If you are looking to see if an arbitrary string exists as a substring of a line, you are better off using string first instead of string match, since the latter has a few metacharacters (especially [ and ], which denote a set of characters) that can cause problems if you're not expecting them.
Try:
if {[string first [string toupper $var1] [string toupper $data]] >= 0} {
# The substring was there...
}
Alternatively, apply relevant backslash quoting when building your search pattern (possibly with string map) or use regexp, which has a useful find-a-literal mode:
if {[regexp -nocase ***=$var1 $data]} {
# The substring was there...
}
The ***= means “the rest of this pattern is a literal string to match” and we can pass -nocase as an option to allow us to not need to use string toupper.
How can I remove a part of the text file if the pattern I am searching is matched?
eg:
pg_pin (VSS) {
direction : inout;
pg_type : primary_ground;
related_bias_pin : "VBN";
voltage_name : "VSS";
}
leakage_power () {
value : 0;
when : "A1&A2&X";
**related_pg_pin** : VBN;
}
My pattern is related_pg_pin. If this pattern is found i want to remove that particular section(starting from leakage power () { till the closing bracket}).
proc getSection f {
set section ""
set inSection false
while {[gets $f line] >= 0} {
if {$inSection} {
append section $line\n
# find the end of the section (a single right brace, #x7d)
if {[string match \x7d [string trim $line]]} {
return $section
}
} else {
# find the beginning of the section, with a left brace (#x7b) at the end
if {[string match *\x7b [string trim $line]]} {
append section $line\n
set inSection true
}
}
}
return
}
set f [open data.txt]
set g [open output.txt w]
set section [getSection $f]
while {$section ne {}} {
if {![regexp related_pg_pin $section]} {
puts $g $section
}
set section [getSection $f]
}
close $f
close $g
Starting with the last paragraph of the code, we open a file for reading (through the channel $f) and then get a section. (The procedure to get a section is a little bit convoluted, so it goes into a command procedure to be out of the way.) As long as non-empty sections keep coming, we check if the pattern occurs: if not, we print the section to the output file through the channel $g. Then we get the next section and go to the next iteration.
To get a section, first assume we haven't yet seen any part of a section. Then we keep reading lines until the end of the file is found. If a line ending with a left brace is found, we add it to the section and take a note that we are now in a section. From then on, we add every line to the section. If a line consisting of a single right brace is found, we quit the procedure and deliver the section to the caller.
Documentation:
! (operator),
>= (operator),
append,
close,
gets,
if,
ne (operator),
open,
proc,
puts,
regexp,
return,
set,
string,
while,
Syntax of Tcl regular expressions
Syntax of Tcl string matching:
* matches a sequence of zero or more characters
? matches a single character
[chars] matches a single character in the set given by chars (^ does not negate; a range can be given as a-z)
\x matches the character x, even if that character is special (one of *?[]\)
Here's a "clever" way to do it:
proc unknown args {
set body [lindex $args end]
if {[string first "related_pg_pin" $body] == -1} {puts $args}
}
source file.txt
Your data file appears to be Tcl-syntax-compatible, so execute it like a Tcl file, and for unknown commands, check to see if the last argument of the "command" contains the string you want to avoid.
This is clearly insanely risky, but it's fun.
In TCL, I have declared an array sstr with some patterns and I would like to match that patterns with the cryplist. If I found that match, I am displaying with array key and the matched list member. But the below program is not working. Hope I did some mistake in the declaration of regular expression.
#!/bin/tclsh
set cryplist [list "$:adzctg-cm20decadt/sr" "$:yyzpty-cm23febadt/sr" "dc*aed1740.0*gbp" "dc*ars1*usd" "dc*gbp10.00*/r" "d|t|lbb/den" "d|t|ordphx"]
array set sstr {
z "dc*[a-z]{3}*"
dl "d\$*[0-9]"
fd "\$:[a-z]{6}"
md "d|t|[a-z]{3}\/[a-z]{3}"
ms "d|t|[a-z]{6}"
}
foreach i $cryplist {
puts "------------- $i --------------"
foreach {n str} [array get sstr] {
puts "$n -> $str"
if { [regexp {$str} $i ] } {
puts "============= $n -> $i ================"
break
}
}
}
The problem is that you're using regexp {$str} $i, which makes the regular expression be the literal $str and not the contents of the str variable. Change to regexp -- $str $i and it should work; the -- says “no further options” (just for safety) and the unquoted $str reads from the variable for that argument (what you want).
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.