I am trying to map a file handle like file6
(the result of a previous set fh [open somefile.txt w]) to the file it was accessing.
So I'd like a mapping between file6 --> somefile.txt
I tried file channels, but this only lists the channel names - not the actual file name.
As you've noticed, Tcl doesn't keep this information for you, but you can easily keep track of it for yourself by doing something like this:
set fh [open somefile.txt w]
set filenames($fh) somefile.txt
Then when you want to know the associated file name, you can
puts $filenames($fh)
You can automate this by e.g.:
proc myOpen {name args} {
global filenames
set fh [open $name {*}$args]
set filenames($fh) $name
return $fh
}
This is of course a quick-and-dirty semi-solution that leaves some important things open, like for instance how the association needs to be similarly removed if the channel is closed. It is possible, but a bit complicated, to create a more comprehensive solution.
Documentation:
global,
open,
proc,
puts,
return,
set,
{*} (syntax)
Related
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 do I read the section after end-of-stream (^Z) in a Tcl-script being sourced?
So far I got info script returning the filename of the currently sourced script which I could open just like any file and put the read position to after end-of-stream by just parsing the file.
In theory the content of the file could change between the invocation of source and subsequent info script and open, possibly causing temporal inconsistency between read script and binary data.
Is there a magic command for this that I've missed? Or do we rely on users/administrators making sure such inconsistencies can't happen?
Suggestion
Provide for your custom source that extracts the trailer in the same I/O step as sourcing the contained script. For example:
interp hide {} source source
proc ::source {fp} {
set size [file size $fp]
set chan [open $fp r]
info script $fp
try {
chan configure $chan -eofchar {\u001a {}}
set script [read $chan]
uplevel 1 [list eval $script]
set scriptOffset [chan tell $chan]
if {$scriptOffset < $size} {
chan seek $chan 1 current; # move cursor beyond eof
chan configure $chan -translation binary
set trailer [read $chan]
# do whatever you want to do with the trailer
}
} finally {
close $chan
}
}
Some remarks
The trick is to employ the same machinery as Tcl's source does internally: configure -eofchar.
Once it has been determined, that there is a trailer (i.e., content beyond the eof char), seek is used to position the cursor at the script's offset.
A second read will then get you the trailer.
From this point onwards, you must be careful to maintain the trailer value in its shape as byte array.
Disclaimer: Tcl wizards like Donal might have better ways of doing so. Also, single-file distribution mechanisms like starkits might have helpers for dealing with script trailers.
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.
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.
My Tcl application should read and store a lot of configurations parameters. I'd like to use regular disk file as a storage rather than registry or something else.
It would be great to store parameters hierarchically. All my parameters are strings, numbers, and lists of them. Configuration file(s) may be placed in directory (not only user's home). Normally application expects configuration file in the current directory.
Do you know any ready-to-use Tcl library?
More general question: what is the "Tcl-way" to read/write application configuration?
Thanks.
If the configuration does not necessarily need to be human-readable, I suggest you consider Sqlite -- it began as a Tcl extension, and therefore Tcl's Sqlite bindings are more mature than any other language's.
See: http://www.sqlite.org/tclsqlite.html
If you don't need random access (that is, configuration files are not huge and each can be slurped completely at once) and don't require processing by external tools, you could just use flat text files containing, say, Tcl lists. The "trick" is that in Tcl each value must have a valid string representation (when asked) and can be reconstructed from its string representation. You get that for free, that is, no special package is required and all you have to provide is some sort of structure to bind serialized values to their names.
To demonstrate:
set a "a string"
set b 536
set c {this is a list {with sublist}}
proc cf_write {fname args} {
set fd [open $fname w]
chan config $fd -encoding utf-8
set data [list]
foreach varName $args {
upvar 1 $varName var
lappend data [list $varName $var]
}
puts $fd $data
close $fd
}
proc cf_read fname {
set fd [open $fname]
chan config $fd -encoding utf-8
set data [read $fd]
close $fd
set data
}
set cfile [file join [file dir [info script]] conf.txt]
cf_write $cfile a b c
foreach entry [cf_read $cfile] {
lassign $entry name value
puts "$name: $value"
}
You'll get this output:
a: a string
b: 536
c: this is a list {with sublist}
Now if you feel like having something more fancy or "interoperable", look at YAML or JSON (you'll need to write a serializer for this one though) or INI formats--all available from Tcllib and hence are plain Tcl.
Even more fancier could be using XML via TDOM (an expat-based C extension). SQLite, which has been already proposed, is even more capable than that (provides random access to the data, is able to operate on huge data arrays). But it seems that for your task these tools appear to be too heavy-weight.
Note that my example deliberately opts to show how to store/restore an arbitrary ad-hoc list of variables so the cf_write procedure builds the Tcl list to be stored by itself. Of course, no one prevents you from building one yourself, providing for creation of hierarchical structures of arbitrary complexity. One caveat is that in this case you might (or might not) face a problem of deconstructing the restored list. But if you'll stick to a general rule of each element being a name/value pair as in my example, the deconstruction shouldn't be hard.
tcllib contains a package inifile for handling windows .ini file format configuration files. As it's part of tcllib it should be avaialble on all platforms (I've just checked and it loads ok on my Solaris 8 box). It allows you to both read and write .ini files and access the configuration by section and key.