I am trying to open a file for read, asking input from user from a Tk file open dialog box, but facing an Error “cannot file channel named”
Here is my code.
Can you let me know the issue with below code?
proc load_input_entries {} {
global sa sd sb sc
set types {
{{Text Files} {.txt} }
{{CSV Files} {.csv} }
{{All Files} * }
}
set fp [tk_getOpenFile -parent . \
-title "Select File" \
-filetypes $types -multiple true \
-initialdir "/simulation/safe/ip/work" ]
if {[file exists $fp]} {
set stuff [read $fp]
set lines [split $stuff "\n"]
set sa [lindex $lines 0]
set sb [lindex $lines 1]
set sc [lindex $lines 2]
set sd [lindex $lines 3]
}
}
tk_getOpenFile gives you the file name. You still have to open the file to be able to read it. Try
set filename [tk_getOpenFile ...
if {[file exists $filename]} {
set fp [open $filename]
...
If you get a problem like this, it's often useful to temporarily insert a puts command to see what the value of your variable is. If you had done that, you would have seen that it had a file name instead of a file handle.
Related
I have a question few days ago ,but I think my expression is not clear and I separate my question into many small questions.
I have many files of process and it contain versions, I have regexp certain line of them and import them into a txt file , the txt format is like
#process #AA_version #BB_version
a11 Aa/10.10-d87_1 Bb/10.57-d21_1
a15 Aa/10.15-d37_1 Bb/10.57-d28_1
a23 Aa/10.20-d51_1 Bb/10.57-d29_3
and each process correspond its AA_version and BB_version
I want to write a tcl named get_tool_version.tcl to show /modify(not replace) the content
If I tclsh get_tool_version.tcl and input process and it will read the txt file and show it's
AA_version=Aa/
BB_version=Bb/
and then I can modify the string of AA and BB version
there is my code
set fp [open tool_version r+]
set file_data [read $fp]
close $fp
set data [split $file_data "\n"]
#input the process
set name [gets stdin] ->#and it'll show correspond AAand BB version
but I don't know how to show it's AA_version and BB_version
and how to modify them.
Or I need to use array?
thanks
Here's a way:
set fh [open tool_version r]
set data [dict create]
while {[gets $fh line] != -1} {
regexp {(\w+)\s+Aa/(\S+)\s+Bb/(\S+)} $line -> process aa bb
dict set data $process Aa $aa
dict set data $process Bb $bb
}
close $fh
set name a15 ;# you would get input from user here
puts "process = $name; Aa = [dict get $data $name Aa]; Bb = [dict get $data $name Bb]"
process = a15; Aa = 10.15-d37_1; Bb = 10.57-d28_1
The Tcl regex syntax is here: https://www.tcl-lang.org/man/tcl8.6/TclCmd/re_syntax.htm
here's my final version
set fp [open tool_version r]
set process [gets stdin]
while {[gets $fh line] != -1} {
if (regexp $process $line) {
dict set process1 Aa: [lindex $line 1]
dict set process1 Bb: [lindex $line 2]
puts "Aa: [lindex $line 1]"
puts "Bb: [lindex $line 2]"
}
}
close $fp
Thanks~
My script is as below. I would like to print out a warning if I cant glob any thing from the directory.
How do I print a warning in the foreach loop when it cant find any file that match the $food and $toy combination?
foreach value $list {
set file1 [ open "$food/$toys/abc.txt"]
set list1 [glob -nocomplain -type f /a/$food/$toys/*]
puts $file1 [join $list1 \n]
close $file1
}
You'd also want to catch errors from open (Speaking of which, you need to open the file for writing which you're not doing), not just glob.
Something like:
# You don't use value in your posted code. Typo or something?
foreach value $list {
try {
set list1 [glob -type f "/a/$food/$toys/*"]
set file1 [open "$food/$toys/abc.txt" w]
puts $file1 [join $list1 \n]
close $file1
} on error {msg} {
puts stderr "Error while processing '$value': $msg"
}
}
will handle both cases, and any other errors that might be raised.
I have developed a TCL UI with couple of inputs needs to be entered by user. First time user will enter all files path but then i wanted to save the user defined entries in a file and then later load it.
Saving is fine... i think of saving all these variables in a file, but loading it from a file needs a mapping, how it can be done ?
Any example will be helpful
If you have the flexibility to define the format of the file where the contents will be stored, I'd recommend storing the contents in a way such that reading/writing maps to the keys and is order independent. This will allow you to update your UI to add/delete input fields without worrying about the order in which they are captured in the file.
For e.g., your file format could be something this:
Top Directory: <value>
LEF File: <value>
.
.
.
You'll have to carefully choose a separator between the key (label) and the value.
If this is going to be used in TCL always, you can make it simpler by storing an array in a file. This'll also speed up when you load the file to populate the entries in the UI. For e.g., your file format could be something this:
set inputFields("Top Directory") <value>
set inputFields("LEF File") <value>
I achieved this by following code, though not very optimized.
First i am saving a input file with variable values and then reading them in same order.
proc save_input_entries {} {
global ENTRYfilename ENTRYfilename2 ENTRYfilename3 ENTRYfilename4 ENTRYfilename5 ENTRYfilename6 ENTRYfilename7 ENTRYfilename8 ENTRYfilename9 ENTRYfilename10 ENTRYfilename11 ENTRYfilename12 ENTRYfilename13 ENTRYfilename14 ENTRYfilename15 ENTRYfilename16 ENTRYfilename17 topdir corner_dir corner_name
set filename Input_entries.txt
set fileId [open $filename "w"]
puts $fileId $ENTRYfilename
puts $fileId $ENTRYfilename3
puts $fileId $ENTRYfilename4
puts $fileId $ENTRYfilename5
puts $fileId $ENTRYfilename7
puts $fileId $ENTRYfilename8
puts $fileId $ENTRYfilename15
puts $fileId $ENTRYfilename14
puts $fileId $ENTRYfilename16
puts $fileId $ENTRYfilename17
close $fileId
}
proc load_input_entries {} {
global ENTRYfilename ENTRYfilename2 ENTRYfilename3 ENTRYfilename4 ENTRYfilename5 ENTRYfilename6 ENTRYfilename7 ENTRYfilename8 ENTRYfilename9 ENTRYfilename10 ENTRYfilename11 ENTRYfilename12 ENTRYfilename13 ENTRYfilename14 ENTRYfilename15 ENTRYfilename16 ENTRYfilename17
set fp [open Input_entries.txt]
set stuff [read $fp]
set lines [split $stuff "\n"]
set ENTRYfilename [lindex $lines 0]
set ENTRYfilename3 [lindex $lines 1]
set ENTRYfilename4 [lindex $lines 2]
set ENTRYfilename5 [lindex $lines 3]
set ENTRYfilename7 [lindex $lines 4]
set ENTRYfilename8 [lindex $lines 5]
set ENTRYfilename15 [lindex $lines 6]
set ENTRYfilename14 [lindex $lines 7]
set ENTRYfilename16 [lindex $lines 8]
set ENTRYfilename17 [lindex $lines 9]
}
I have a script which generates a numbered file..
like this:
set no 0
file fn [open log_file_$no w]
I want to remember this no every time I run the script, i.e when running for the first time, the file name should be log_file_0 , 2nd time it should be log_file_1 etc.
Is there a way to "remember" the value of the variable so that it can be used later?
You need to store the value to disk somehow. Hoodiecrow gives you one sensible way to do it: in the actual filename. Other options:
in a config file somewhere
in a database (sqlite is good for this)
Demo for (1)
# read the value
if {[file exists num.dat]} {
set fh [open num.dat r]
set no [read -nonewline $fh]
close $fh
} else {
set no 0
}
set logfile log_file_${no}
# ...
# write the value
set fh [open num.dat w]
puts $fh [incr no]
close $fh
Demo for (2)
package require Tcl 8.6
package require tdbc::sqlite3
set config_file config.db
tdbc::sqlite3::connection create db $config_file
# read the value
if {[file exists $config_file]} {
set stmt [$db prepare "create table config (number integer)"]
$stmt execute
$stmt close
set no 0
} else {
set stmt [$db prepare "select number from config limit 1"]
$stmt foreach row {
set no [dict get $row number]
}
$stmt close
}
# ...
# write the value
set stmt [$db prepare "update config set number=:new limit 1"]
$stmt execute [dict create new [incr no]]
$stmt close
$db close
You don't need a variable, you have the number you need in the file list:
set no [scan [lindex [lsort -dictionary [glob log_file_*]] end] log_file_%2d]
incr no
set fn [open log_file_$no w]
Let's break that up a bit. Create a list of log files:
set filelist [glob log_file_*]
Sort the list in dictionary order (where 10 comes after 2) and pick the last element:
set highest_numbered [lindex [lsort -dictionary $filelist] end]]
Extract the number from the file name:
set no [scan $highest_numbered log_file_%2d]
Increase the number and open a new log file:
incr no
set fn [open log_file_$no w]
If there is a possibility that no log files exist, the glob command will fail. To take care of this case, either do this:
set filelist [glob -nocomplain log_file_*]
if {[llength $filelist]} {
set highest_numbered [lindex [lsort -dictionary $filelist] end]]
set no [scan $highest_numbered log_file_%2d]
} else {
set no 0
}
incr no
set fn [open log_file_$no w]
or this slightly safer version (if you have Tcl 8.6):
try {
glob log_file_*
} on ok filelist {
set highest_numbered [lindex [lsort -dictionary $filelist] end]
set no [scan $highest_numbered log_file_%2d]
} trap {TCL OPERATION GLOB NOMATCH} {} {
set no 0
} on error message {
puts stderr "error when listing log files: $message"
set no -1
}
if {$no > -1} {
incr no
set fn [open log_file_$no w]
# ...
chan close $fn
}
Documentation: chan, glob, if, incr, lindex, lsort, open, scan, set, try
(Note: the 'Hoodiecrow' mentioned elsewhere is me, I used that nick earlier.)
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
}