Writing multiple lines to a file in TCL - tcl

I'm looking to modify gpsfeed+ to add in a section which writes the NAV string out to a text file while the simulator is running. The tool is written in tcl and I'm at a loss as to what I need to do. What I have so far is:
if {$prefs(udp) & $::udpOn} {
# opens file to write strings to
set fp [open "input_NAV.txt" w+]
# one sentence per udp packet
foreach line [split $::out \n] {
puts $fp $line
}
close $fp
}
Right now if UDP broadcast is switched on, I want to take each NAV string broadcast over UDP and write it to a file. But the code above only writes 1 of the strings and then overwrites the string. I've been trying to add in a /n switch, but I've not had any joy.

I was using the wrong mode for opening the file:
w+ Open the file for reading and writing. Truncate it if it exists. If it does not exist, create a new file.
I should have been using either of the following:
a Open the file for writing only. If the file does not exist, create a new empty file. Set the file pointer to the end of the file prior to each write.
a+ Open the file for reading and writing. If the file does not exist, create a new empty file. Set the initial access position to the end of the file.

This would be a comment, but formatting.
This code:
foreach line [split $::out \n] {
puts $fp $line
}
Is equivalent to:
puts $fp $::out

Related

Can we skip a specfic command while tcl is running source.?

Eg. get_abc, get_xyz command which generate collection, set_pqr receives collection are custom commands and following is tcl file
#Tcl file start
get_abc
get_xyz
set_pqr -object [get_abc]
#Tcl file end
Now the requirement is we need to skip the set_pqr command and this tcl file is big and read only, we can't change it.
Now we added this handling in set_pqr command callback to skip processing, but still get_abc command in same line get processed which is anyway to be discarded and not needed when the collection goes to set_pqr. Also we can't skip get_abc from software because its valid and can be used at other places.
Does tcl provides capability to skip the full line of set_pqr
The only ways to make Tcl do what you want are to not use standard source to load the command. Let's write our own version instead:
proc skippingSource {filename} {
set f [open $filename]
set code [read $f]
close $f
# Comment out the offending lines; this is a cheap hack BTW
set code [regsub -all -line {^set_pqr } $code "#set_pqr "]
# Override [info script] until this procedure returns
info script $filename
uplevel 1 $code
}
Now we just need to use skippingSource instead of source. We can of course call it directly, the easiest method, or we can substitute in for source:
# Keep the original in case we want to put it back
rename source originalSource
rename skippingSource source
You redefine set_pqr to a proc that does nothing:
# Back up original set_pqr
rename set_pqr set_pqr_original
# Make new proc that does nothing
proc set_pqr {args} {}
Now you can source the file and each seq_pqr command will do nothing (and takes an arbitrary number of arguments).
When you need the original command back again:
rename set_pqr_original set_pqr
This whole thing could be wrapped up in one proc too:
proc source_with_skip {filename "skip_commands {}"} {
foreach command $skip_commands {
rename $command ${command}_original
}
source $filename
foreach command $skip_commands {
rename ${command}_original $command
}
}
% source my_file.tcl set_pqr
Note that the above may not be sufficient if the procs are in different namespaces than the current namespace.

How to close file opened via file readable in Tcl

When the input file is corrupt, the input file is held open by my code and I am unable to delete it. To delete it, I might close the command line which ran my code so the file is closed automatically then I am able to delete.
But what is the command to close a file opened via file readable?
I'm not sure if this is the best way to do it, but I think you could use something like this:
proc close_all_files {} {
foreach channel [file channels "file*"] {
close $channel
}
}
Then call close_all_files when you need to close files that have been previously opened.
Warning: this will close absolutely all files opened within the script. There are not a lot of options if you don't know the file identifier that was created by file readable, unless you modify that proc to add the file identifier to an accessible list outside the proc, the easiest example being to use a global list:
proc file'readable name {
global filesIds
set rc [catch {open $name} fp]
if {[string match "file*" $fp]} {
lappend filesIds $fp
}
if {$rc==0} {close $fp}
expr {$rc==0}
}
And then if you know the file order for which you used file readable, you can pick which one you need to close.

collecting set of files using tcl script

I am looking to generate a tcl script, which reads each line of a file, say abc.txt; each line of abc.txt is a specific location of set of files which need to be picked except the ones commented.
For example abc.txt has
./pvr.vhd
./pvr1.vhd
// ./pvr2.vhd
So I need to read each line of abc.txt and pick the file from the location it has mentioned and store it in a separate file except the once which starts with "//"
Any hint or script will be deeply appreciated.
The usual way of doing this is to put a filter at the start of the loop that processes each line that causes the commented lines to be skipped. You can use string match to do the actual detecting of whether a line is to be filtered.
set f [open "abc.txt"]
set lines [split [read $f] "\n"]
close $f
foreach line $lines {
if {[string match "//*" $line]} {
continue
}
# ... do your existing processing here ...
}
This also works just as well when used with a streaming loop (while {[gets $f line] >= 0} {…}).

in tcl, how to edit string in the open file?

let's say that I have opened a file using:
set in [open "test.txt" r]
I'm intend to revise some string in the certain line, like:
style="fill:#ff00ff;fill-opacity:1"
and this line number is: 20469
And I want to revise the value ff00ff to other string value like ff0000.
What are the proper ways to do this? Thanks in advance!
You need to open the file in read-write mode; the r+ mode is probably suitable.
In most cases with files up to a reasonable number of megabytes long, you can read the whole file into a string, process that with a command like regsub to perform the change in memory, and then write the whole thing back after seeking to the start of the file. Since you're not changing the size of the file, this will work well. (Shortening the file requires explicit truncation.)
set f [open "test.txt" r+]
set data [read $f]
regsub {(style="fill:#)ff00ff(;fill-opacity:1)"} $data {\1ff0000\2} data
seek $f 0
puts -nonewline $f $data
# If you need it, add this here by uncommenting:
#chan truncate $f
close $f
There are other ways to do the replacement; the choice depends on the details of what you're doing.

Tcl seek and write in a file opened with 'a+'

I need to store some logs in a file that can grow with every execution. A logical way would be to use a+ option when opening because using w+ would truncate the file. However, with the a+ option (Tcl 8.4) I cannot write anywhere in the file. seek works fine. I can verify that the pointer was moved using tell. But the output is always done at the tail end of the file.
Is there any way to resolve this? I.e. having the ability to seek and write in any place and also preserve the old file at the open.
In Tcl 8.5, the behavior of Tcl on Unix was changed so that the O_APPEND flag is passed to the open() system call. This causes the OS to always append the data to the file, and is inherited when the FD is passed to subprocesses; for logs, it is exactly the right thing. (In 8.4 and before, and in all versions on Windows, the behavior is simulated inside Tcl's file channel implementation, which will internally seek() to the end immediately before the write(); that obviously is subject to potential problems with race conditions when there are multiple processes logging to the same file and is definitely unsafe when the FD is passed to subprocesses.) You can manage truncation of the opened file with chan truncate (new in 8.5), which works just fine on a+-opened files.
If you do not want the seek-to-end behavior, you should not use a+ (or a). Try r+ or some combination of flags, like this:
set f [open $filename {RDWR CREAT}]
For comparison, the a+ option is now exactly the same as the flags RDWR CREAT APPEND, and not all combinations of longer flags can be described by short form flag specifiers. If you're not specifying APPEND, you'll need to do the seek $f 0 end yourself (and watch out for problems with multiple processes if you're appending to logs; that's when APPEND becomes required and exceptionally hard to correctly simulate any other way).
Open with r+ - it opens in read mode (thus not turncating the file) but allows writing as well.
See the documentation of open for more info: http://www.tcl.tk/man/tcl8.5/TclCmd/open.htm
I have verified that using the a+ option allow me to read/write anywhere in the file. However, by writing in the middle (or at the beginning) of a file, I overwrite the data there, not inserting. The following code illustrate that point:
#!/usr/bin/env tclsh
# Open the file, with truncation
set f [open foo w]
puts $f "one"
puts $f "two"
close $f
# Open again, with a+ ==> read/write/append
set f [open foo a+]
puts $f "three" ;# This goes to the end of the file
seek $f 4 ;# Seek to the beginning of the word "two"
puts $f "2.0" ;# Overwrite the word "two"
close $f
# Open and verify the contents
set f [open foo r]
puts [read $f]
close $f
Output:
one
2.0
three
If you are looking to insert in the middle of the file, you might want to look at the fileutil package, which contains the ::fileutil::insertIntoFile command.