TCL Input and Output
Most input and output operations in Tcl are done by means of the puts and gets
commands. Most of the examples in this document have made use of the puts command to display output
on the console. In a similar manner, the gets command can be used to wait for input from the console, and optionally
store it an a variable. Its general syntax has the following form:
gets channelId ?varName?
The first argument to gets is the name of an open channel from which to read data, and can be thought
of as a file descriptor in the C sense. If the varName argument is specified, gets
stores the data it reads in that variable, and returns the number of bytes read. If varName is not specified, then gets
simply returns the data it read.
puts and gets variations for SCL
Some special considerations must be made for the puts and gets commands when running TCL scripts through the TCL interpreter that has been integrated into Surpac. The when the puts command is used without an explicit channel identifier the output is sent to the message window. When the puts command is used with a channel identifier (a file created using open command) output is sent to that file.
The gets command can only be used with a channel identifier because there is no console window (like a DOS prompt window) available from which input can be accepted. The gets command exhibits normal behaviour when a channel identifier is the first argument to the command. Input from the user is obtained by using the SclCreateGuido command and other associated commands. An example of this can be found in the SCL tutorial that follows on from this TCL tutorial.
Example 9.1: - This example will not work with the Surpac TCL interpreter
puts -nonewline "Enter your name: " set bytesread [gets stdin name] puts "Your name is $name, and it is $bytesread bytes long"
Output: (note that user input is shown in italics)
Enter your name: Shyam
Your name is Shyam, and it is 5 bytes long
Example 9.1 makes use of both the puts and gets commands. The puts
command is used with the -nonewline flag to suppress the trailing newline that it normally appends to its output. A variable, "bytesread",
is then assigned the result of a gets command that reads from the channel "stdin" (the standard input),
and stores the data it reads in the variable, "name". Thus "bytesread" ends up storing the number of bytes of user input read from the console.
In Example 9.1, gets was used to read from the channel "stdin" (created automatically when
the Tcl interpreter is started) which corresponds to the standard input. The puts command can also be
used with a channel identifier to write to a specific channel. However, if no channel identifier is passed to puts,
it writes to the standard output (this is the way puts has been used throughout this document). In addition to the standard input
and output, channels can also be created to read from other types of files. As illustrated by Example 9.2, the open
command can be used to open a channel to a file, and obtain an appropriate identifier for the channel. This identifier can then be passed to gets
to read from the file, or puts to write to the file.
Example 9.2:
Opening a file on a Windows system
set f [open "c:/temp/myfile" "w"] puts $f "We live in Texas. It's already 110 degrees out here." puts $f "456" close $f
Output: (none)
Opening a file on a UNIX system
set f [open "/tmp/myfile" "w"] puts $f "We live in Texas. It's already 110 degrees out here." puts $f "456" close $f
Output: (none)
Commentary on differences
The convention for the path delimiter is to use a "/" (forward slash) for UNIX and a "\" backslash for Windows. From the examples above you will note that the "/" forward slash has been used for both. The reasons for this are twofold:
- TCL kindly takes care of the "/" to "\" conversion for you. This means that you can write a script for UNIX and run it on a Windows system without change. The only thing that can't be taken care of in the same manner is the drive designator, i.e. "c:". without an explicit drive designator the file will be opened on the current drive.
- Used by itself a "\" has the effect of escaping the next character. In the example above the first "\" is followed by the letter "t". This gives rise to the "\t" character which TCL sees as a TAB character. The second "\" is followed by "m". Since "\m" is not one of the special characters it has no real effect. The result of this of course is not what is required.
Reccommendation for path delimiter
You should always use the "/" as the path delimiter because TCL takes care of platform idiosyncracies for you and because it ultimately is less confusing. If you insist on using the "\" character as a path delimiter, I strongly advise against it as it can be a source of problems, then you must use 2 "\"'s whenever you just want one. Thus the Windows example above will work with this open statement.
set f [open "c:\\temp\\myfile" "w"]
This example uses the open command to open a channel to a file called "/tmp/myfile" or
"c:/temp/myfile". The syntax of the open command can take on three forms, one of which is:
open name ?access?
The access argument specifies what type of access (for example, read-only access or read-write access) to the file given by name
is desired. See the manual page for the open command for a complete
description of the access modes. In this case, write-only access is desired, so the value "w" is given for the access argument.
The open command returns a channel identifier that can be used with gets and puts
to read and write from the file. In Example 9.2, this identifier is stored in the variable, "f". The puts
command is then used to write two strings to the file, and then the close command is used to close the file.
Special notes on open
File names and directory names with embedded spaces (e.g. "Program Files") cause no problems for the open command.
Case in file names is treated according to the underlying operating system. That is, on Windows the file names "Abc" and "abc" are considered identical even though they may be displayed differently. On UNIX "Abc" and "abc" are considered unique and this must be considered when dealing with files on UNIX systems.
Example 9.3 reads the file created in Example 9.2, and displays its contents.
Example 9.3:
set f [open "/tmp/myfile" "r"] set line1 [gets $f] set len_line2 [gets $f line2] close $f puts "line 1: $line1" puts "line 2: $line2" puts "Length of line 2: $len_line2"
Output:
line 1: We live in Texas. It's already 110 degrees out here.
line 2: 456
Length of line 2: 3
The file, "/tmp/myfile", is opened in read-only mode with the open command. The gets
command is then used with the channel identifier returned by open to read from the file. The first call to gets
does not give it the name of a variable in which to store the data it reads, so this data is returned instead. Command substitution is used
to store it in the variable, "line1". The second call to gets tells it to store the data it reads in the variable, "line2". Therefore, gets
would return the number of bytes it read, which, by means of command substitution, is stored in the variable "len_line2". Because all the data has
been read, the file is then closed.
In this case, it was known that the file contained only two lines of data. If the length of the file was not known, the eof
command could be used with a while loop to read until the end of the file was reached.
Example 9.3: Using scan to read formatted data
set f [open "data.txt" "r"]
while {[gets $f line] >= 1} {
set count [scan $line "%f, %f, %f" x y z ]
if {$count != 3} {
error "Error reading input - terminating script"
}
puts "Input data x=$x, y=$y, z=$z"
}
close $f
The file (data.txt) that this script reads looks like this:
1,2,3 -10,11,12 201,3001,-10999
Output:
Input data x=1.0, y=2.0, z=3.0 Input data x=-10.0, y=11.0, z=12.0 Input data x=201.0, y=3001.0, z=-10999.0
The file "data.txt" is opened in read-only mode using the open command. The gets command is used, in a while loop to read data from the file a line at a time. Each line is then scanned using the known format of the file. Any line that doesn't match the format of the file, determined by a failure to read some or all of the values from the line (scan returns a count of the successful conversions), causes an error and the script will terminate by using the error command.
Finally the data read from the file is output using the puts command to display the information that has been read from the file.
This data displayed using the puts command in this example can be modified by using the format command to format the information in an appropriate manner. Try substituting the puts command with the following variations of the format command.
- puts [format "%.3f %.3f %.3f" $x $y $z]
Use 3 decimal places in a field just wide enough giving the following output.
1.000 2.000 3.000 -10.000 11.000 12.000 201.000 3001.000 -10999.000
Use 3 decimal places in a 10 character wide field to make things line up nicely to give the following output.
1.000 2.000 3.000 -10.000 11.000 12.000 201.000 3001.000 -10999.000
Use 3 decimal places in a 10 character wide field and explicit signage, "+" for >= 0 and "-" for < 0 to give the following output.
+1.000 +2.000 +3.000 -10.000 +11.000 +12.000 +201.000 +3001.000 -10999.000
Use 3 decimal places in a 10 character wide field and explicit signage, "+" for >= 0 and "-" for < 0 and use zero fill for the entire 10 character field to give the following output.
+000001.000 +000002.000 +000003.000 -000010.000 +000011.000 +000012.000 +000201.000 +003001.000 -010999.000