Skip to content
VLSI Mentor

Tcl Part I · Module 12 · Files, Directories, and Paths

Channels: How Tcl Talks to a File

This is the first module where your script reaches outside the interpreter, and Tcl makes that easier than you might expect by having exactly one idea for it. A channel is a thing you can read from or write to — a file, the terminal, a pipe to another program. You have been using channels since Chapter 1 without being told: every puts you have written sent text to one. This chapter makes that explicit, shows how to get a channel of your own by opening a file, and establishes the obligation that comes with it, which is that a channel you open is a resource you have to give back.

Foundation11 min readTclPart IFilesChannelsI/O

Part I · Module 12 · Chapter 12.1 · Channels: How Tcl Talks to a File

1. You already have three

Tcl starts with three channels open:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts [lsort [file channels]]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
stderr stdin stdout

stdin is where input arrives, stdout is normal output, and stderr is for errors. Every puts you have written since Chapter 1.7 has been writing to stdout — the command simply defaults to it when you do not say otherwise:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "this goes to stdout"
puts stdout "so does this, explicitly"
puts stderr "this goes to stderr"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
this goes to stdout
so does this, explicitly
this goes to stderr

All three lines appeared here because a terminal shows both streams, but they are separate: redirect one in a shell and the other still reaches you. That is the whole reason stderr exists.

2. Opening a file gives you another

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set fp [open sample.txt w]
puts $fp "line one"
puts $fp "line two"
close $fp

puts "the file now holds [file size sample.txt] bytes"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
the file now holds 18 bytes

open takes a filename and a mode and returns a channel handle — a short token naming the channel it just created. Everything afterwards refers to the channel by that handle, not by the filename.

Two parallel left-to-right flows. A file channel is created by open, which returns a handle used with gets, puts and read, and is finished with close. The standard channels stdin, stdout and stderr are already open, take the same commands with no handle to create, and are left open.openreturns a handleThe handlegets, puts, readclosereleases itstdin, stdoutalready openSame commandsno handle to makeNo closeleave them alone12
Figure 1 — the two kinds of channel and what each one requires of you. A channel you open yourself is created by open, which hands back a handle; every read and write names that handle, and close releases it when you are finished. The three standard channels already exist when your script starts, take exactly the same commands, and are not yours to close.

3. The same commands work on any channel

This is the payoff of having one idea. puts writes to a channel, whether that channel is a file or your terminal:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set fp [open sample.txt w]

puts stdout "to the terminal"
puts $fp    "to the file"

close $fp
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
to the terminal

Only one line appeared, because the other went into the file. Nothing about puts changed — only which channel it was given.

Reading is the same story. gets takes a channel and reads one line from it, and that channel can be stdin as easily as a file.

4. Modes

open takes a second argument saying what you intend to do:

ModeMeaning
rread; the file must exist. This is the default if you omit the mode.
wwrite; truncates an existing file, creates it if absent
aappend; writes go to the end, creates it if absent
r+read and write; the file must exist

The mode is enforced. Writing to a channel opened for reading fails:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set fp [open sample.txt r]
puts $fp "nope"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
channel "file5" wasn't opened for writing

That file5 is the handle, and the number will differ for you — which is section 2's point arriving in an error message.

Chapter 12.5 deals with w versus a properly, because the truncation is more dangerous than it looks.

5. close is an obligation

Every channel you open must be closed. Three reasons, in increasing order of how likely they are to bite you:

Your output may not be on disk until you close. Writes are buffered — Chapter 12.6 demonstrates a file sitting at zero bytes while its data waits in memory.

A process can only have so many channels open at once. A loop that opens a file per iteration and never closes will eventually run out.

The file stays locked or incomplete for anything else trying to read it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set fp [open sample.txt w]
puts $fp "important"
close $fp

Three lines, and the third is not optional.

6. Making close reliable

If something between open and close raises an error, the close never runs. The shape that survives that uses catch — which you met in Chapter 7.3 §4:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc first_line {path} {
    set fp [open $path r]
    set code [catch {
        gets $fp line
        set line
    } result]
    close $fp
    if {$code} { return -code error $result }
    return $result
}

set fp [open sample.txt w] ; puts $fp "the first line" ; close $fp
puts [first_line sample.txt]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
the first line

The close runs whether the body succeeded or failed, and a failure is still reported. That is the pattern to reach for whenever a procedure opens something.

7. What a channel is not

Two clarifications worth having early.

A channel is not the file. It is your connection to it. Two channels can be open on the same file at once, each with its own position.

A handle is not a path. You cannot pass $fp to file size or file exists — those take filenames:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set path sample.txt
set fp [open $path r]

puts "size via the path   : [file size $path]"
close $fp
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
size via the path   : 15

Keep the path in its own variable when you will need both.

8. A worked example

Copying a file, one line at a time, with both channels managed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc copy_file {from to} {
    set in [open $from r]
    set out [open $to w]

    set n 0
    while {[gets $in line] >= 0} {
        puts $out $line
        incr n
    }

    close $in
    close $out
    return $n
}

set fp [open source.txt w]
puts $fp "alpha"
puts $fp "beta"
puts $fp "gamma"
close $fp

puts "copied [copy_file source.txt dest.txt] lines"
puts "dest.txt is [file size dest.txt] bytes"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
copied 3 lines
dest.txt is 17 bytes

Two channels, two handles, two closes. The loop is the canonical reading form and Chapter 12.2 takes it apart properly.

9. Exercises

Exercise 1. Which three channels exist before your script does anything?

Exercise 2. What does open return, and why should you not hard-code the value?

Exercise 3. What is the difference between puts "x" and puts $fp "x"?

Exercise 4. What error do you get writing to a channel opened with mode r?

Exercise 5. Give three reasons to close a channel you opened.

Exercise 6. Why can you not pass a channel handle to file size?

Answers

1. stdin, stdout and stderr.

2. A channel handle — a token such as file5. The number depends on how many channels have been opened in that interpreter, so it varies between runs and machines. Keep the value open returned in a variable and use that.

3. puts "x" writes to stdout, which is the default channel. puts $fp "x" writes to the channel whose handle is in fp. The command is the same; only the destination differs.

4. channel "fileN" wasn't opened for writing, where N is whatever number that handle has.

5. Buffered output may not have reached the disk until you close; a process can only hold a limited number of open channels, so a loop that leaks them will eventually fail; and the file may be incomplete or locked for anything else reading it.

6. Because a handle names your connection to a file, not the file itself. file size and file exists take a path. Keep the path in its own variable if you need both.

10. Remember

  • A channel is anything you can read from or write to — a file, the terminal, later a pipe.
  • stdin, stdout and stderr already exist. Every puts so far has written to stdout.
  • open path mode returns a handle such as file5; the number varies, so always keep it in a variable.
  • The same commands work on any channel — puts stdout and puts $fp differ only in destination.
  • Modes: r read (must exist, the default), w write and truncate, a append, r+ read and write.
  • The mode is enforced — channel "fileN" wasn't opened for writing.
  • Close what you opened, and do not close the three standard channels.
  • Wrap open…close in catch so an error in between does not leak the channel.
  • A handle is not a path. file size takes the filename, not the handle.

Where this fits

Part of the Tcl curriculum.