Tcl/tk binary output differs from input - tcl

The script creates a binary file containing the binary data but the output is wrong, it adds binary data. What is happening?
#!/usr/bin/tclsh
if 0 {
# One pixel BMP image
0000000 4d42 003a 0000 0000 0000 0036 0000 0028
0000010 0000 0001 0000 0001 0000 0001 0018 0000
0000020 0000 0004 0000 104d 0000 104d 0000 0000
0000030 0000 0000 0000 b8b8 00e9
000003a
}
set hex1 "424d3a00000000000000360000002800"
set hex2 "00000100000001000000010018000000"
set hex3 "0000040000004d1000004d1000000000"
set hex4 "000000000000b8b800e9"
set fp [open text.bin w]
set outBinData [binary format H* $hex1$hex2$hex3$hex4]
puts "Format done: $outBinData"
puts -nonewline $fp $outBinData
close $fp
set fp [open text.bin r]
set inBinData [read $fp]
close $fp`

You're forgetting to tell tcl the channel is for binary, not textual, data so it doesn't do things like converting to an encoding like UTF-8 or on Windows do line ending conversions.
Need
chan configure $fp -encoding binary
after you open the file. Or use set fp [open text.bin wb] instead.
Same thing applies when opening a binary file for reading.

Related

Binary scan tcl

I have problem with my code because of not all line from file are scanning .
It stopped after 1000 attempt. I am trying to scan all binary lines from wav file. When binary scan starts to return important value for me I don't know why eof occur (a lot bytes left to read).
set fh [open $file r]
binary scan [read $fh 12] A4iA4 sig1 len sig2
if {$sig1 != "RIFF" || $sig2 != "WAVE"} {
close $fh;
return -code error "Not a WAV file"
}
binary scan [read $fh 24] A4issiiss id size format channels samplerate byterate align bitrate
binary scan [read $fh 8] A4i data sampletoread
set len [expr {[file size $file] - [tell $fh] - 8 - ($size - 16)}]
set str [ list ]
while {1} {
if {[eof $fh]} break
binary scan [read $fh 1000] c* str
puts "$str"
}
}
The problem is that you've opened the file in text mode, not binary mode, so input stops at the first ^Z character (which is the official ASCII text EOF character; this is in fact useful in some use-cases, though not in yours). Since you're reading binary data, you should open it as such by passing the b open mode flag:
set fh [open $file rb]
If you're using an older version of Tcl, you instead (and equivalently) do:
set fh [open $file r]
fconfigure $fh -translation binary
The b flag is just a (very) convenient shortcut. (The second way works on all Tcl versions back to 8.0; you didn't want to handle binary data at all in Tcl directly before then.)

Tcl could not save floating point numbers in binary format

I am trying to save a list of numbers in binary format (floating point single)
but Tcl cant save it correctly and I could not gain to correct number when i read the file from vb.net
set outfile6 [open "btest2.txt" w+]
fconfigure stdout -translation binary -encoding binary
set aa {}
set p 0
for {set i 1} {$i <= 1000 } {incr i} {
lappend aa [expr (1000.0/$i )]
puts -nonewline $outfile6 [binary format "f" [lindex $aa $p]]
incr p
}
close $outfile6
Tcl cant save it correctly
There are two glitches in your snippet:
the missing brackets for nested command evaluation around lindex (see my comment): [lindex $aa $p]
you fconfigured the stdout, rather than your file channel: fconfigure $outfile6 -translation binary
With this fixed, the following works for me:
set outfile6 [open "btest2.txt" w+]
fconfigure $outfile6 -translation binary
set aa {}
set p 0
for {set i 1} {$i <= 1000 } {incr i} {
lappend aa [expr (1000.0/$i )]
puts -nonewline $outfile6 [binary format "f" [lindex $aa $p]]
incr p
}
close $outfile6
Suggestions for improvement
Your snippet seems overly complicated to me, esp. the loop construct. Simplify to:
Better use [scan %f $value] to explicitly turn a value into the floating point representation, rather than [expr]?
[binary format] takes a counter or wildcard, like f*, to process multiple values: [binary format "f*" $aa]
You don't need the loop variables p, use [lindex $aa end]; or better a loop variable to hold the single added element (rather than to collect it from the list again).
-translation binary implies -encoding binary

Change content of binary file

I want to change the content of a (large) binary file without reading and writing the whole file content. This is what I tried out:
#!/usr/bin/env tclsh
set f [open MyFile.txt w+]
fconfigure $f -translation binary
foreach c [split "MyText is ABC" ""] {
puts -nonewline $f $c
}
seek $f 2 "start"
foreach c [split "Name" ""] {
puts -nonewline $f $c
}
close $f
I create a file and change Text into Name. That works fine.
But if the file already exists and I want to change Text into Name with the following program
#!/usr/bin/env tclsh
set f [open MyFile.txt w+]
fconfigure $f -translation binary
seek $f 2 "start"
foreach c [split "Name" ""] {
puts -nonewline $f $c
}
close $f
it doesn't work. The reason is that open filename w+ truncates the file. Does someone knows how to change the content of an existing binary file?
Thank you in advance.
Since this is a large binary file and you're not wanting to change the size much, it's best to use the open mode-string r+b; r+ is read-write without truncation (w+ is read-write with initial truncation, a+ is read-append, so every write goes to the end of the file) and b means “binary mode”. Then use seek to move to where you want to write and puts -nonewline to write the binary string out. Binary strings can be made in a number of ways, but key ones are binary format and encoding convertto.
# Open the file to write
set f [open "foobar.bin" "r+b"]
# Pick your offset to write to
seek $f 10240
# Writes a pair of 4-byte integers in little-endian form
puts -nonewline $f [binary format "i2" {123 345}]
close $f
The above little script updates the file foobar.bin to have a particular 8 bytes at 10kiB in. Those eight bytes are the little-endian 4-byte words for 123 and 345.

Tcl to write a file using csv

I need help writing a tcl, which reads portions of data from a csv file and write into a text file in the following pattern.
NAME :
FROM= -100 -346 -249 -125
TO= -346 -249 -125 100
COLOR= COLOR1 COLOR2 COLOR3 COLOR4
NAME will be a fixed row,
FROM and TO information should be retreived from csv file and
COLOR information can be hardcoded array of colors from the Tcl itself.
From csv data below, the first value(-100) under MIN will be the first value(-100) under FROM of text file. The last value(100) from excel MAX column will be the last value(100) under text file TO column. The values under VALUE column in excel will be rounded and used as TO and FROM per pattern shown.
Data VALUE
100 -345.8756
200 -249.3654
300 -125.3554
COUNT MIN MAX
1 -100 -98
93 84 86
98 94 96
99 96 98
100 98 100
Some pointers:
reading lines from a file
use set fields [regexp -inline -all {\S+} $line] to split the line into words separated by arbitrary whitespace
you'll need to keep a couple of boolean flags as a state machine to determine what to do with the current line (are you collecting values or looking for the min/max)
use [expr {round([lindex $fields end])}] to round the values: see https://tcl.tk/man/tcl8.6/TclCmd/expr.htm#M22
See if that gets you started.
package require struct::matrix
package require csv
package require fileutil
array set OPTS {
csv_input_filename output/stackoverflow.csv
txt_output_filename output/stackoverflow.txt
colors {COLOR1 COLOR2 COLOR3 COLOR4}
}
set output_format {NAME :
FROM= %s %s %s %s
TO= %s %s %s %s
COLOR= %s
}
try {::struct::matrix xdata} on error {} {xdata destroy; ::struct::matrix xdata}
set chan [open $OPTS(csv_input_filename)]
csv::read2matrix $chan xdata , auto
close $chan
csv::joinlist [xdata get rect 0 0 end end]
fileutil::writeFile $OPTS(txt_output_filename) \
[format $output_format [xdata get cell 1 5] \
[expr {round([xdata get cell 1 1])}] [expr {round([xdata get cell 1 2])}] \
[expr {round([xdata get cell 1 3])}] [expr {round([xdata get cell 1 1])}] \
[expr {round([xdata get cell 1 2])}] [expr {round([xdata get cell 1 3])}] \
[xdata get cell 2 9] [list {*}$OPTS(colors)]]
xdata destroy
fileutil::cat [file native $OPTS(txt_output_filename)]
# NAME :
# FROM= -100 -346 -249 -125
# TO= -346 -249 -125 100
# COLOR= COLOR1 COLOR2 COLOR3 COLOR4
Note:
The script should result to the expected output .txt file, assuming the .csv file is contained within the output subfolder from within the current running directory of course.
Assuming the script filename is 'matrix_csv_extract.tcl' you would simply source the tcl script interactively to have it run:
source matrix_csv_extract.tcl

TCL: Write binary/logical data to file

I am trying to write a list of 1's or 0's to file in tcl. I expect the most efficient way would be to write this in binary format to use the least amount of bits possible, especially because I anticipate dealing with many megabytes of data. I am following examples from:
https://groups.google.com/forum/#!msg/comp.lang.tcl/HrC-VlfRL_E/PAQdLRTyrMEJ
http://wiki.tcl.tk/1180
but when I go to read my binary data in as per the examples, I literally get back the work "binary". What is going on?
Examples above use the syntax
[format binary c1 0 1 1]
but should transpose 'format' and 'binary'
[binary format c1 0 1 1]
as per https://www.tcl.tk/man/tcl8.5/TclCmd/binary.htm#M4
Example Script that gives the desired results (tcl 8.5 maybe other versions):
set fp [open text.bin w]
set outBinData [binary format ccc 1 0 1 ]
puts "Format done: $outBinData"
puts -nonewline $fp $outBinData
close $fp
set fp [open text.bin r]
set inBinData [read $fp]
close $fp
binary scan $inBinData ccc val1 val2 val3
puts "Scan done: $val1 $val2 $val3"