i'm using some simulator that uses Tcl for transcript commands (Questa sim)
i want to echo file content like "cat" command in unix.
can it be done in one line command at tcl? is it possible to "cat" just the 5 first lines of file
In one line
puts [read [open data.dat r]]
OR step by step..
set handle [open data.dat r]
puts [read $handle]
close $handle
To open a file and echo its contents to standard output (just like cat), do this:
set f [open $filename]
fcopy $f stdout
close $f
To just do the first five lines (which is just like head -5), use this procedure:
proc head {filename {lineCount 5}} {
set f [open $filename]
for {set i 0} {$i < $lineCount} {incr i} {
if {[gets $f line] >= 0} {
puts $line
}
}
close $f
}
It takes more work because it's more complex to detect line endings than it is to just ship bytes around.
Here is the following code, to read 5 lines at a time from a given file.
#!/usr/bin/tclsh
set prev_count -1
set fp [open "input-file.txt" "r"]
set num_lines [split [read $fp] \n]
for {set i 4} {$i < [llength $num_lines]} { incr i 5} {
set line_5 [lrange $num_lines [incr prev_count] $i ]
set prev_count $i
puts "$line_5\n\n"
}
Related
I have 2 files having only one column. Say file1.txt and file2.txt.
Below are the contents inside the file
Inside file1.txt
Tom
Harry
Snowy
Edward
Inside file2.txt
Harry
Tom
Edward
2) I want to write a code that will check each item in the column and print something as below.
"Tom, Harry, Edward" are present in both the files
Snowy is there in file1.txt but not in file2.txt
3) Basic code
set a [open file1.txt r]
set b [open file2.txt r]
while {[gets $a line1] >= 0 && [gets $b line2] >= 0} {
foreach a_line $line1 {
foreach b_line $line2 {
if {$a_line == $b_line } {
puts "$a_line in file test1 is present in $b_line in file test2\n"
} else {
puts "$a_line is not there\n"
}
}
}
}
close $a
close $b
Issue is that it is not checking each name in the column.
Any suggestions.
Thanks in advance.
Neel
What you want to do is read each file separately and not have nested loops:
# read the contents of file1 into an associative array
# store the user as an array **key** for fast lookoup
set fh [open "file1.txt" r]
while {[gets $fh user] != -1} {
set f1tmp($user) ""
}
close $fh
# read file2 and compare against file1
array set users {both {} file1 {} file2 {}}
set fh [open "file2.txt" r]
while {[gets $fh user] != -1} {
if {[info exists f1tmp($user)]} {
lappend users(both) $user
unset f1tmp($user)
} else {
lappend users(file2) $user
}
}
close $fh
set users(file1) [array names f1tmp]
parray users
users(both) = Harry Tom Edward
users(file1) = Snowy
users(file2) =
Or as Donal suggests, use tcllib
package require struct::set
set fh [open file1.txt r]
set f1users [split [read -nonewline $fh] \n]
close $fh
set fh [open file2.txt r]
set f2users [split [read -nonewline $fh] \n]
close $fh
set results [struct::set intersect3 $f1users $f2users]
puts "in both: [join [lindex $results 0] ,]"
puts "f1 only: [join [lindex $results 1] ,]"
puts "f2 only: [join [lindex $results 2] ,]"
in both: Harry,Tom,Edward
f1 only: Snowy
f2 only:
In TCL Scripting:
I have a file in that i know how to search a string but how to get the line number when string is found.please answer me if it is possible
or
set fd [open test.txt r]
while {![eof $fd]} {
set buffer [read $fd]
}
set lines [split $buffer "\n"]
if {[regexp "S1 Application Protocol" $lines]} {
puts "string found"
} else {puts "not found"}
#puts $lines
#set i 0
#while {[regexp -start 0 "S1 Application Protocol" $line``s]==0} {incr i
#puts $i
#}
#puts [llength $lines]
#puts [lsearch -exact $buffer S1]
#puts [lrange $lines 261 320]
in the above program i am getting the output as string found .if i will give the string other than in this file i am getting string not found.
The concept of 'a line' is just a convention that we layer on top of the stream of data that we get from a file. So if you want to work with line numbers then you have to calculate them yourself. The gets command documnetion contains the following example:
set chan [open "some.file.txt"]
set lineNumber 0
while {[gets $chan line] >= 0} {
puts "[incr lineNumber]: $line"
}
close $chan
So you just need to replace the puts statement with your code to find the pattern of text you want to find and when you find it the value of $line gives you the line number.
To copy text that lies between two other lines I'd use something like the following
set chan [open "some.file.txt"]
set out [open "output.file.txt" "w"]
set lineNumber 0
# Read until we find the start pattern
while {[gets $chan line] >= 0} {
incr lineNumber
if { [string match "startpattern" $line]} {
# Now read until we find the stop pattern
while {[gets $chan line] >= 0} {
incr lineNumber
if { [string match "stoppattern" $line] } {
close $out
break
} else {
puts $out $line
}
}
}
}
close $chan
The easiest way is to use the fileutil::grep command:
package require fileutil
# Search for ipsum from test.txt
foreach match [fileutil::grep "ipsum" test.txt] {
# Each match is file:line:text
set match [split $match ":"]
set lineNumber [lindex $match 1]
set lineText [lindex $match 2]
# do something with lineNumber and lineText
puts "$lineNumber - $lineText"
}
Update
I realized that if the line contains colon, then lineText is truncated at the third colon. So, instead of:
set lineText [lindex $match 2]
we need:
set lineText [join [lrange $match 2 end] ":"]
suppose my file contains
we must greap the ep
the whole ep
endpoint: /usr/home/bin/tcl_
giga/hope (v)
beginpoint" /usr/home/bin/lp50 (^)
I only want to print the endpoint path i.e. /usr/home/bin/tcl_giga/hope in one line.
Can anyone help me regarding the same. Actually i have write my code like :-
set fp [open "text" "r+"]
while {![eof $fp]} {
gets $fp line
puts $line
if {[regexp {endpoint:} $line]} {
set new_line $line
puts $new_line
}
}
But that is only printing the 1st endpoint line.
This is one way to do it. It allows you to change the number of lines to print after an "endpoint:" line.
set printNMore 0
while {[gets $fp line] >= 0} {
if {[string first "endpoint:" $line] >= 0} {
set printNMore 2
}
if {$printNMore > 0} {
puts $line
incr printNMore -1
}
}
The simplest way to do that line-at-a-time is something like this:
set fp [open "text" "r+"]
while {[gets $fp line] >= 0} {
if {[string match "endpoint: *" $line]} {
puts -nonewline [string range $line 10 end]
# Plus the whole of the next line...
puts [gets $fp]
}
}
However, if I was doing this myself I'd slurp the whole file in at once and use a regular expression:
set fp [open "text" "r+"]
set contents [read $fp]
if {[regexp {endpoint: ?([^\n]+)\n([^\n]*)} -> bit1 bit2]} {
# Print the concatenation of the capture groups
puts "$bit1$bit2"
}
I could write a better pattern if I knew what the file format was more precisely…
I am trying to write a tcl script in which I need to insert some lines of code after finding a regular expression .
For instance , I need to insert more #define lines of codes after finding the last occurrence of #define in the present file.
Thanks !
When making edits to a text file, you read it in and operate on it in memory. Since you're dealing with lines of code in that text file, we want to represent the file's contents as a list of strings (each of which is the contents of a line). That then lets us use lsearch (with the -regexp option) to find the insertion location (which we'll do on the reversed list so we find the last instead of the first location) and we can do the insertion with linsert.
Overall, we get code a bit like this:
# Read lines of file (name in “filename” variable) into variable “lines”
set f [open $filename "r"]
set lines [split [read $f] "\n"]
close $f
# Find the insertion index in the reversed list
set idx [lsearch -regexp [lreverse $lines] "^#define "]
if {$idx < 0} {
error "did not find insertion point in $filename"
}
# Insert the lines (I'm assuming they're listed in the variable “linesToInsert”)
set lines [linsert $lines end-$idx {*}$linesToInsert]
# Write the lines back to the file
set f [open $filename "w"]
puts $f [join $lines "\n"]
close $f
Prior to Tcl 8.5, the style changes a little:
# Read lines of file (name in “filename” variable) into variable “lines”
set f [open $filename "r"]
set lines [split [read $f] "\n"]
close $f
# Find the insertion index in the reversed list
set indices [lsearch -all -regexp $lines "^#define "]
if {![llength $indices]} {
error "did not find insertion point in $filename"
}
set idx [expr {[lindex $indices end] + 1}]
# Insert the lines (I'm assuming they're listed in the variable “linesToInsert”)
set lines [eval [linsert $linesToInsert 0 linsert $lines $idx]]
### ALTERNATIVE
# set lines [eval [list linsert $lines $idx] $linesToInsert]
# Write the lines back to the file
set f [open $filename "w"]
puts $f [join $lines "\n"]
close $f
The searching for all the indices (and adding one to the last one) is reasonable enough, but the contortions for the insertion are pretty ugly. (Pre-8.4? Upgrade.)
Not exactly the answer to your question, but this is the type of task that lends towards shell scripting (even if my solution is a bit ugly).
tac inputfile | sed -n '/#define/,$p' | tac
echo "$yourlines"
tac inputfile | sed '/#define/Q' | tac
should work!
set filename content.txt
set fh [open $filename r]
set lines [read $fh]
close $fh
set line_con [split $lines "\n"]
set line_num {}
set i 0
foreach line $line_con {
if [regexp {^#define} $line] {
lappend line_num $i
incr i
}
}
if {[llength $line_num ] > 0 } {
linsert $line_con [lindex $line_num end] $line_insert
} else {
puts "no insert point"
}
set filename content_new.txt
set fh [open $filename w]
puts $fh file_con
close $fh
How can I read a file and put the elements in it to a list and write the contents to other file?
the file contents are
this is my house and it is very good
this is my village and it is the best
good and best has to be repeated 10 times.
help me if possible
Did you mean something like that?
set fi [open "filename"]
set fo [open "outputfile" w]
while {[gets $fi line]!=-1} {
if {$line!=""} {
for {set i 0} {$i<10} {incr i} {
puts $fo $line
}
}
}
close $fi
close $fo
Your question is unclear.
Do you mean the lines containing "good" or "best" need to be repeated 10 times?
set fin [open infile r]
set fout [open outfile w]
while {[gets $fin line] != -1} {
switch -glob -- $line {
*good* -
*best* {
for {set i 1} {$i <= 10} {incr i} {
puts $fout $line
}
}
default {
# what to do for lines NOT containing "good" or "best"?
}
}
}
close $fin
close $fout
If you mean the actual words need to be repeated 10 times:
while {[gets $fin line] != -1} {
puts $fout [regsub -all {\y(good|best)\y} $line {& & & & & & & & & &}]
}
An example for that regsub command:
set str "these goods are good; that bestseller is the best"
puts [regsub -all {\y(good|best)\y} $str {& & &}]
# prints: these goods are good good good; that bestseller is the best best best
Update
Based on your comments, you might want:
array set values {good 10 best 20}
while {[gets $fin line] != -1} {
regsub -all {\y(good|best)\y} $line {&-$values(&)} line
puts $fout [subst -nobackslashes -nocommands $line]
}