Suppose I have a file1 with a few queries inside,
Query 1
Query 2
Query 3
And I have a normal text file2 containing a bunch of data
Data 1 Query 1 something something
Data something Query 2 something something
Something Query 3 something something
Data1 continue no query
Data2 continue no query
How do I create a loop such that it ignores the lines containing queries from file1 and prints only lines without the queries in the file? so in this case only these values gets printed
Data1 continue no query
Data2 continue no query
i tried producing the results using this loop script i made
Storing the queries to be ignored from file1 into $wlistItems
set openFile1 [open file1.txt r]
while {[gets openFile1 data] > -1} {
set wlist $data
append wlistItems "{$wlist}\n"
}
close $openFile1
Processing file2 to print lines without ignored queries
set openFile2 [open file2.txt r]
while {[gets $openFile2 data] > -1} {
for {set n 0} {$n < [llength $wListItems]} {incr n} {
if {[regexp -all "[lindex $wListItems $n]" $data all value]} {
continue
}
puts $data
}
}
close $openFile2
However, the script does not skip the lines. It instead prints out repeated data from file2.
while {[gets $openFile2 data] > -1} {
set found 0
for {set n 0} {$n < [llength $wListItems]} {incr n} {
if {[regexp -all "[lindex $wListItems $n]" $data all value]} {
set found 1
break
}
}
if {!$found} {
puts $data
}
}
A simpler solution:
package require fileutil
set queries [join [split [string trim [::fileutil::cat file1]] \n] |]
::fileutil::foreachLine line file2 {
if {![regexp ($queries) $line]} {
puts $line
}
}
The first command (after the package require) reads the file with the queries and packs them up as a set of branches (Query 1|Query 2|Query 3). The second command processes the second file line by line and prints those lines that don't contain any of those branches.
Documentation: fileutil package, if, join, package, puts, Syntax of Tcl regular expressions, regexp, set, split, string
I'd just do this:
puts [exec grep -Fvf file1 file2]
Related
I want to calculate the average for this column with tcl
please help me
frame Elec
1 50
2 40
3 30
4 20
If this is for a standalone script, (Warning: Self promotion ahead), I wrote a program called tawk that's like awk except using TCL for scripting, which does most of the work for you:
$ tawk 'line {$NR > 1} { incr sum $F(2) }
END { puts [expr {double($sum) / ($NR - 1)}] }' input.txt
35
# Equivalent awk:
$ awk 'NR > 1 { sum += $2 } END { print (sum / (NR - 1)) }' input.txt
35
If it's part of a larger program, you have to open the file and read and split lines yourself. Maybe something like
# Column number is 1-based
proc avg_column {filename column} {
set f [open $filename r]
gets $f ;# Read and discard header line
set sum 0
set nlines 0
while {[gets $f line] >= 0} {
set columns [regexp -all -inline {\S+} $line]
incr sum [lindex $columns $column-1]
incr nlines
}
close $f
return [expr {double($sum) / $nlines}]
}
puts [avg_column input.txt 2]
Not an answer, but some tips. You need to:
open the file
read the header with gets
use a while loop to read lines of the file
use split or regexp to get the 2nd field
sum the values (and count the lines) with expr, or incr if the values are only integers
If your input happens to be (some sort of CSV), or you can steer it into this direction, then you may use tcllib's csv package:
package require csv
package require struct::matrix
struct::matrix dm
set f [open mydata.csv]
while {[gets $f l] >= 0} {
# sanitize input, line-wise
set l [regsub -all {\s+} $l " "]
csv::split2matrix dm $l " " auto
}
close $f
set columnData [lrange [dm get column 1] 1 end]; # strip off header
puts [expr {double([tcl::mathop::+ {*}$columnData])/[llength $columnData]}]; # compute avg
Some hints:
gets will read your input file line by line;
csv::split2matrix puts each line into a struct::matrix;
/matrix/ get column /n/ gives access to one data column (incl. header field);
tcl::mathop::+ gives access to the built-in addition operator (outside of the [expr] command) and supports 2+ summands.
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 have a set of fields to parse from a file and Im doing it line by line inside a foreach loop, i want to know how i can skip a line and go to the next line
For example : if encounter a string called "ABC", i need to grab a number in the next line,
some characters "ABC"
123
The problem is I'm actually having a lot of numbers in the file but i need to grab a number, specifically the number which is after a line break after the string "ABC".
How can i do this
?
It's a bit easier to do with a while loop, reading one line at a time, since you can then easily read an extra line when you find your trigger case (assuming you don't have a run of lines with "ABC" in them):
set fd [open $theFilename]
while {[gets $fd line] >= 0} {
if {
[string match *"ABC"* $line]
&& [gets $fd line] >= 0
&& [regexp {\d+} $line -> num]
} then { # I like to use 'then' after a multi-line conditional; it's optional
puts "Found number $num after \"ABC\""
}
}
close $fd
The reason this is awkward with foreach is that it will always process the same number of elements each time through the loop.
If you're dealing with data which can have the run-of-lines issue alluded to above, you are actually better off with foreach curiously enough:
set fd [open $theFilename]
set lines [split [read $fd] \n]
close $fd
foreach line $lines {
incr idx; # Always the index of the *next* line
if {
[string match *"ABC"* $line]
&& [regexp {\d+} [lindex $lines $idx] -> num]
} then {
puts "Found number $num after \"ABC\""
}
}
This works because when you do lindex of something past the end, it produces the empty string (which won't match that simple regular expression).
You can try this simple solution
set trigger 0
set fh [open "your_file" "r"]
while {[gets $fh line] != -1} {
if {[regexp -- {"ABC"} $line]} {
incr trigger
continue
}
if {$trigger > 0} {
puts $line ; # or do something else
incr trigger -1
}
}
close $fh
I am writing a code to grep a regular expression pattern from a file, and output that regular expression and the number of times it has occured.
Here is the code: I am trying to find the pattern "grep" in my file hello.txt:
set file1 [open "hello.txt" r]
set file2 [read $file1]
regexp {grep} $file2 matched
puts $matched
while {[eof $file2] != 1} {
set number 0
if {[regexp {grep} $file2 matched] >= 0} {
incr number
}
puts $number
}
Output that I got:
grep
--------
can not find channel named "qwerty
iiiiiii
wxseddtt
lsakdfhaiowehf'
jbsdcfiweg
kajsbndimm s
grep
afnQWFH
ACV;SKDJNCV;
qw qde
kI UQWG
grep
grep"
while executing
"eof $file2"
It's usually a mistake to check for eof in a while loop -- check the return code from gets instead:
set filename "hello.txt"
set pattern {grep}
set count 0
set fid [open $filename r]
while {[gets $fid line] != -1} {
incr count [regexp -all -- $pattern $line]
}
close $fid
puts "$count occurrances of $pattern in $filename"
Another thought: if you're just counting pattern matches, assuming your file is not too large:
set fid [open $filename r]
set count [regexp -all -- $pattern [read $fid [file size $filename]]]
close $fid
The error message is caused by the command eof $file2. The reason is that $file2 is not a file handle (resp. channel) but contains the content of the file hello.txt itself. You read this file content with set file2 [read $file1].
If you want to do it like that I would suggest to rename $file2 into something like $filecontent and loop over every contained line:
foreach line [split $filecontent "\n"] {
... do something ...
}
Glenn is spot on. Here is another solution: Tcl comes with the fileutil package, which has the grep command:
package require fileutil
set pattern {grep}
set filename hello.txt
puts "[llength [fileutil::grep $pattern $filename]] occurrences found"
If you care about performance, go with Glenn's solution.
I have a file in here which has multiple set statements. However I want to extract the lines of my interest. Can the following code help
set in [open filename r]
seek $in 0 start
while{ [gets $in line ] != -1} {
regexp (line to be extracted)
}
Other solution:
Instead of using gets I prefer using read function to read the whole contents of the file and then process those line by line. So we are in complete control of operation on file by having it as list of lines
set fileName [lindex $argv 0]
catch {set fptr [open $fileName r]} ;
set contents [read -nonewline $fptr] ;#Read the file contents
close $fptr ;#Close the file since it has been read now
set splitCont [split $contents "\n"] ;#Split the files contents on new line
foreach ele $splitCont {
if {[regexp {^set +(\S+) +(.*)} $ele -> name value]} {
puts "The name \"$name\" maps to the value \"$value\""
}
}
How to run this code:
say above code is saved in test.tcl
Then
tclsh test.tcl FileName
FileName is full path of file unless the file is in the same directory where the program is.
First, you don't need to seek to the beginning straight after opening a file for reading; that's where it starts.
Second, the pattern for reading a file is this:
set f [open $filename]
while {[gets $f line] > -1} {
# Process lines
if {[regexp {^set +(\S+) +(.*)} $line -> name value]} {
puts "The name \"$name\" maps to the value \"$value\""
}
}
close $f
OK, that's a very simple RE in the middle there (and for more complicated files you'll need several) but that's the general pattern. Note that, as usual for Tcl, the space after the while command word is important, as is the space between the while expression and the while body. For specific help with what RE to use for particular types of input data, ask further questions here on Stack Overflow.
Yet another solution:
as it looks like the source is a TCL script, create a new safe interpreter using interp which only has the set command exposed (and any others you need), hide all other commands and replace unknown to just skip anything unrecognised. source the input in this interpreter
Here is yet another solution: use the file scanning feature of Tclx. Please look up Tclx for more info. I like this solution for that you can have several scanmatch blocks.
package require Tclx
# Open a file, skip error checking for simplicity
set inputFile [open sample.tcl r]
# Scan the file
set scanHandle [scancontext create]
scanmatch $scanHandle {^\s*set} {
lassign $matchInfo(line) setCmd varName varValue; # parse the line
puts "$varName = $varValue"
}
scanfile $scanHandle $inputFile
close $inputFile
Yet another solution: use the grep command from the fileutil package:
package require fileutil
puts [lindex $argv 0]
set matchedLines [fileutil::grep {^\s*set} [lindex $argv 0]]
foreach line $matchedLines {
# Each line is in format: filename:line, for example
# sample.tcl:set foo bar
set varName [lindex $line 1]
set varValue [lindex $line 2]
puts "$varName = $varValue"
}
I've read your comments so far, and if I understand you correctly your input data file has 6 (or 9, depending which comment) data fields per line, separated by spaces. You want to use a regexp to parse them into 6 (or 9) arrays or lists, one per data field.
If so, I'd try something like this (using lists):
set f [open $filename]
while {[gets $f line] > -1} {
# Process lines
if {[regexp {(\S+) (\S+) (\S+) (\S+) (\S+) (\S+)} $line -> name source drain gate bulk inst]} {
lappend nameL $name
lappend sourceL $source
lappend drainL $drain
lappend gateL $gate
lappend bulkL $bulk
lappend instL $inst
}
}
close $f
Now you should have a set of 6 lists, one per field, with one entry in the list for each item in your input file. To access the i-th name, for example, you grab $nameL[$i].
If (as I suspect) your main goal is to get the parameters of the device whose name is "foo", you'd use a structure like this:
set name "foo"
set i [lsearch $nameL $name]
if {$i != -1} {
set source $sourceL[$i]
} else {
puts "item $name not found."
set source ''
# or set to 0, or whatever "not found" marker you like
}
set File [ open $fileName r ]
while { [ gets $File line ] >= 0 } {
regex {(set) ([a-zA-Z0-0]+) (.*)} $line str1 str2 str3 str4
#str2 contains "set";
#str3 contains variable to be set;
#str4 contains the value to be set;
close $File
}