Read lines from file exactly as they appear

2019-09-19 17:30发布

问题:

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

回答1:

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}*.



回答2:

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


回答3:

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]]
}


回答4:

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