Tcl, if not not working - tcl

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.

Related

Tcl finding min value in a textfile

i have one Textfile with thousands of values and some alphanumerical chars like this:
\Test1
+3.00000E-04
+5.00000E-04
+4.00000E-04
now i want to scan this file and write the values into variables.
set path "C:/test.txt"
set in [open $path r]
while {[gets $in line] != -1} {
set Cache [gets $in line]
if { $Cache < $Cache } {
set lowest "$Cache"
}
}
has anybody an idea? im getting a alert which tells me the Directory couldnt deleted?!
br
You could use the core math function tcl::mathfunc::min. If there is "junk" (i.e. lines that contain text that aren't numbers), you can filter those lines out first:
set numbers {}
set f [open test.txt]
while {[gets $f line] >= 0} {
if {[string is double -strict $line]} {
lappend numbers [string trim $line]
}
}
close $f
tcl::mathfunc::min {*}$numbers
# => +3.00000E-04
If every line is a valid double precision floating point number, you can dispense with the filtering:
set f [open test.txt]
set numbers [split [string trim [read $f]]]
close $f
tcl::mathfunc::min {*}$numbers
# => +3.00000E-04
If you can use the Tcllib module fileutil, which is easy to pick up from the Tcllib site if not available on your installation (it is included in the ActiveTcl installation already), you can simplify the code somewhat:
package require fileutil
set numbers {}
::fileutil::foreachLine line test.txt {
if {[string is double -strict $line]} {
lappend ::numbers [string trim $line]
}
}
tcl::mathfunc::min {*}$numbers
or
package require fileutil
tcl::mathfunc::min {*}[split [string trim [::fileutil::cat test.txt]]]
Documentation:
>= (operator),
close,
fileutil (package),
gets,
if,
lappend,
namespace,
open,
package,
read,
set,
split,
string,
while,
{*} (syntax),
Mathematical functions for Tcl expressions

I want to search a pattern [Severity Level: Critical] in whole file in tcl

I have tried the below code, but it is checking line by line and want to check it in whole file. Please help me out in writing the correct code, once i get the pattern break it and says pattern is found else pattern is not found
set search "Severity Level: Critical"
set file [open "outputfile.txt" r]
while {[gets $file data] != -1} {
if {[string match *[string toupper $search]* [string toupper $data]] } {
puts "Found '$search' in the line '$data'"
} else {
puts "Not Found '$search' in the line '$data'"
}
}
If the file is “small” with respect to available memory (e.g., no more than a few hundred megabytes) then the easiest way to find if the string is present is to load it all in with read.
set search "Severity Level: Critical"
set f [open "thefilename.txt"]
set data [read $f]
close $f
set idx [string first $search $data]
if {$idx >= 0} {
puts "Found the search term at character $idx"
# Not quite sure what you'd do with this info...
} else {
puts "Search term not present"
}
If you want to know what line it is in, you might split the data up and then use lsearch with the right options to find it.
set search "Severity Level: Critical"
set f [open "thefilename.txt"]
set data [split [read $f] "\n"]
close $f
set lineidx [lsearch -regexp -- $data ***=$search]
if {$idx >= 0} {
puts "Found the search term at line $lineidx : [lindex $data $lineidx]"
} else {
puts "Search term not present"
}
The ***= is a special escape to say “treat the rest of the RE as literal characters” and it's ideal for the case where you can't be sure that the search term is free of RE metacharacters.
The string first command is very simple, so it's easy to use correctly and to work out whether it can do what you want. The lsearch command is not simple at all, and neither are regular expressions; determining when and how to use them is correspondingly trickier.

Using string match to search a file

Want to search within a file using tcl to find a match.
Here is what I have.
set search "random string"
set file [open "file.txt" r]
while {![eof $file]} {
gets $file data
if {[ string match [string toupper $search] [string toupper $data] ] } {
//works
} else {
//doesnt work
}
}
File.txt
chicken.dinner:1439143130
random.strings:1439143130
more random strings:1439413390
random.strings.that.contain-special.characters:1439441566
Not able to match "random string" with what's in the file. Appreciate any help.
If you want to use only string match, then use the glob pattern * here.
set search "random string"
set file [open "file.txt" r]
while {[gets $file data] != -1} {
if {[string match *[string toupper $search]* [string toupper $data]] } {
puts "Found '$search' in the line '$data'"
} else {
# does not match case here
}
}
Output :
Found 'random string' in the line 'more random strings:1439413390'
Since we want to know whether the line contains the search string, we have added * at the beginning as well as in the end. It can match any number of sequence.
Reference : string match

Read lines from file exactly as they appear

I am reading from a file and need to find the exact line $(eval $(call CreateTest KEYWORD and everything following after the line (as the rest is all random). This is how I am currently trying to find it but it always reports back as nothing found to match.
proc listFromFile {$path1} {
set find {$(eval $(call CreateTest, KEYWORD}
upvar path1 path1
set f [open $path1 r]
set data [split [string trim [read $f]] \n]
close $f
# return [lsearch -all -inline $data *KEYWORD*]
return [lsearch -exact -all -inline $data $find*]
}
The commented out line is the closest I can get it to work but it pulls anything with KEYWORD anywhere in the file. the KEYWORD could appear in lines I do not want to read therefore I need to pull the exact line as stated above
EDIT
I should have mentioned that the file is formatted like so;
$(eval $(call CreateTest, KEYWORD ...
$(eval $(call CreateTest, NOT_KEYWORD ...
$(eval $(call CreateTest, KEYWORD ...
$(eval $(call CreateTest, KEYWORD ...
$(eval $(call CreateTest, NOT_KEYWORD ...
$(eval $(call CreateTest, KEYWORD ...
which means I only want to pull the lines containing the exact string and the keyword. But there are lines between what I am looking for that I do not want to display
I think you should just apply your match to each line as you read them.
proc getMatchingLines {filename match} {
set result {}
set f [open $filename r]
while {[gets $f line] != -1} {
if {[string match ${find}* $line]} {
lappend result $line
}
}
close $f
return $result
}
set find {$(eval $(call CreateTest, KEYWORD}
set matching [getMatchingLines $filename $find]
foreach line $matching {
# do something with the matching line
}
You could build up a list of results or do something immediately for each matching line as appropriate for your application. The main difference is that string match doesn't have many meta characters unlike regexp. Only * and ? are special so it is simple to match for a line matching your string followed by anything ie: ${find}*.
Use string first and string range instead:
# foo.tcl
set f [open "data.txt" r]
set body [read $f]
puts -nonewline [string range $body [string first "ccc" $body] [string length $body]]
close $f
Test:
$ cat data.txt
aaa
bbb
ccc
ddd
eee
$ tclsh foo.tcl
ccc
ddd
eee
I think in your code you have used * as a glob pattern.
return [lsearch -exact -all -inline $data $find*]
When -exact flag used, it will treat that * as a literal * thereby failing to get the desired result. Removing that * will solve the problem.
proc listFromFile {$path1} {
set find {$(eval $(call CreateTest, KEYWORD }
upvar path1 path1
set f [open $path1 r]
set data [split [string trim [read $f]] \n]
close $f
return [lsearch -all -inline $data $find]]
}
This should work:
proc listFromFile path {
set f [open $path r]
set data [split [string trim [read $f]] \n]
close $f
return [lsearch -exact -all -inline $data { KEYWORD}]
}
In my answer to your earlier question, I suggested lsearch (without -exact) and KEYWORD* as a pattern because that seemed to be what you were after. Considering the lines you show here, searching for a space character followed by the string KEYWORD seems more likely to work.
Another thing: your problem with the parameter (which you tried to solve with upvar) was that you had a dollar sign attached to the parameter name. If you leave out the dollar sign you get a usable parameter name like in the code above (it is possible to use it even with the dollar sign, but it's a lot harder).
Documentation: close, lsearch, open, proc, read, return, set, split, string

TCL: Check file existance by SHELL environment variable (another one)

I have a file contain lines with path to the files. Sometimes a path contain SHELL environment variable and I want to check the file existence.
The following is my solution:
set fh [open "the_file_contain_path" "r"]
while {![eof $fh]} {
set line [gets $fh]
if {[regexp -- {\$\S+} $line]} {
catch {exec /usr/local/bin/tcsh -c "echo $line" } line
if {![file exists $line]} {
puts "ERROR: the file $line is not exists"
}
}
}
I sure there is more elegant solution without using
/usr/local/bin/tcsh -c
You can capture the variable name in the regexp command and do a lookup in Tcl's global env array. Also, your use of eof as the while condition means your loop will interate one time too many (see http://phaseit.net/claird/comp.lang.tcl/fmm.html#eof)
set fh [open "the_file_contain_path" "r"]
while {[gets $fh line] != -1} {
# this can handle "$FOO/bar/$BAZ"
if {[string first {$} $line] != -1} {
regsub -all {(\$)(\w+)} $line {\1::env(\2)} new
set line [subst -nocommand -nobackslashes $new]
}
if {![file exists $line]} {
puts "ERROR: the file $line does not exist"
}
}
First off, it's usually easier (for small files, say of no more than 1–2MB) to read in the whole file and split it into lines instead of using gets and eof in a while loop. (The split command is very fast.)
Secondly, to do the replacement you need the place in the string to replace, so you use regexp -indices. That does mean that you need to take a little more complex approach to doing the replacement, with string range and string replace to do some of the work. Assuming you're using Tcl 8.5…
set fh [open "the_file_contain_path" "r"]
foreach line [split [read $fh] "\n"] {
# Find a replacement while there are any to do
while {[regexp -indices {\$(\w+)} $line matchRange nameRange]} {
# Get what to replace with (without any errors, just like tcsh)
set replacement {}
catch {set replacement $::env([string range $line {*}$nameRange])}
# Do the replacement
set line [string replace $line {*}$matchRange $replacement]
}
# Your test on the result
if {![file exists $line]} {
puts "ERROR: the file $line is not exists"
}
}
TCL programs can read environment variables using the built-in global variable env. Read the line, look for $ followed by a name, look up $::env($name), and substitute it for the variable.
Using the shell for this is very bad if the file is supplied by untrusted users. What if they put ; rm * in the file? And if you're going to use a shell, you should at least use sh or bash, not tcsh.