Skip to content
VLSI Mentor

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

Append Mode, and Why the Mode Matters

There are two ways to open a file for writing and the difference is not a detail. One of them starts from empty and one of them starts from whatever is already there, which is the obvious part. The part worth a chapter is when the emptying happens: not when you write, but at the instant you open. A script that opens a log with the wrong mode and then takes an early return, or hits an error, or simply finds nothing to report, has already destroyed the previous contents — without writing a byte and without reporting anything.

Foundation10 min readTclPart IFilesAppendModes

Part I · Module 12 · Chapter 12.5 · Append Mode, and Why the Mode Matters

1. The two modes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# w — start from empty
set fp [open notes.txt w]
puts $fp "first run"
close $fp

# w again — starts from empty again
set fp [open notes.txt w]
puts $fp "second run"
close $fp

set fp [open notes.txt r] ; puts -nonewline [read $fp] ; close $fp
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
second run

The first run's line is gone. Now the same sequence with a:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set fp [open log.txt w]
puts $fp "first run"
close $fp

set fp [open log.txt a]
puts $fp "second run"
close $fp

set fp [open log.txt r] ; puts -nonewline [read $fp] ; close $fp
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
first run
second run

a positions every write at the end of whatever is already there.

2. w truncates at open, not at write

This is the fact to take away. Watch the file's size across the two commands:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set fp [open data.txt w]
puts $fp "twenty-two bytes here"
close $fp
puts "before      : [file size data.txt] bytes"

set fp [open data.txt w]
puts "just opened : [file size data.txt] bytes"
close $fp
puts "after close : [file size data.txt] bytes"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
before      : 22 bytes
just opened : 0 bytes
after close : 0 bytes

Nothing was written between the open and the close. The file was emptied anyway, the instant it was opened.

3. a creates the file too

Append mode does not require the file to exist:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "exists before : [file exists fresh.log]"

set fp [open fresh.log a]
puts $fp "first entry"
close $fp

puts "exists after  : [file exists fresh.log], [file size fresh.log] bytes"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
exists before : 0
exists after  : 1, 12 bytes

So a is safe as a first write as well as a subsequent one. A logging procedure never needs to check whether the log exists yet.

4. Choosing

You wantMode
A file that reflects only this run — a report, a generated outputw
A record that accumulates across runs — a log, an audit traila
To add to a file you did not createa
To be sure stale content cannot survivew

The question to ask is whether the previous contents are yours to destroy. A report you generate every run is; a log someone may be tailing is not.

5. A logging procedure

The canonical shape, which opens and closes per call:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc log_line {path text} {
    set fp [open $path a]
    puts $fp $text
    close $fp
}

file delete -force run.log

log_line run.log "started"
log_line run.log "step one complete"
log_line run.log "finished"

set fp [open run.log r] ; puts -nonewline [read $fp] ; close $fp
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
started
step one complete
finished

Opening and closing for every line is more work than holding the channel open, and it has two advantages that usually outweigh that: the data is on disk after every call (Chapter 12.6 explains why that is not automatic), and the script can be interrupted at any point without losing the log.

For a hot loop writing thousands of lines, hold the channel open instead and flush periodically.

6. r+ and the modes you will rarely need

open accepts a few more:

ModeMeaning
rread only; must exist (the default)
wwrite; truncate or create
aappend; create if needed
r+read and write; must exist; does not truncate
w+read and write; truncates
a+read and append; creates if needed

r+ is the one worth knowing about, because it is the only way to open an existing file for writing without emptying it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set fp [open existing.txt w] ; puts $fp "original content" ; close $fp
puts "before : [file size existing.txt] bytes"

set fp [open existing.txt r+]
puts "with r+: [file size existing.txt] bytes (not truncated)"
close $fp
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
before : 17 bytes
with r+: 17 bytes (not truncated)

In practice, modifying a file in place is rarely what you want — reading it, changing the data, and writing a new file is easier to get right and easier to recover from. Chapter 12.4 §7's write-to-temporary-then-rename is the shape to reach for.

7. A worked example

A script that keeps a log and regenerates a report, using the right mode for each:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
file delete -force build.log summary.txt

proc note {text} {
    set fp [open build.log a]       ;# accumulates
    puts $fp $text
    close $fp
}

proc write_summary {rows} {
    if {[llength $rows] == 0} { return 0 }
    set fp [open summary.txt w]     ;# replaced each run
    foreach {name value} $rows {
        puts $fp [format "%-10s %s" $name $value]
    }
    close $fp
    return [expr {[llength $rows] / 2}]
}

note "run started"
set n [write_summary {cycles 12000 errors 0}]
note "summary written: $n rows"
note "run finished"

puts "--- build.log ---"
set fp [open build.log r] ; puts -nonewline [read $fp] ; close $fp
puts "--- summary.txt ---"
set fp [open summary.txt r] ; puts -nonewline [read $fp] ; close $fp
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--- build.log ---
run started
summary written: 2 rows
run finished
--- summary.txt ---
cycles     12000
errors     0

The log grows; the summary is replaced. And write_summary checks before opening, so a run with nothing to report leaves the previous summary intact.

8. Exercises

Exercise 1. What is the difference between modes w and a?

Exercise 2. At what moment does w empty an existing file?

Exercise 3. What is wrong with this, and what is the fix?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc write_report {path rows} {
    set fp [open $path w]
    if {[llength $rows] == 0} { close $fp ; return "nothing to do" }
    ...
}

Exercise 4. Does mode a require the file to exist?

Exercise 5. Write a log_line procedure that appends one line to a path.

Exercise 6. Which mode opens an existing file for writing without truncating it, and why is it usually not the right answer anyway?

Answers

1. w starts from an empty file, discarding any previous contents. a positions writes at the end of whatever is already there, so the file grows. Both create the file if it does not exist.

2. At open, before any puts runs. A script that opens with w and then writes nothing still leaves the file empty.

3. The open happens before the emptiness check, so a call with no rows truncates the file and then returns "nothing to do" — destroying the previous report while claiming to have done nothing. The fix is to check first and open only once there is something to write.

4. No. a creates the file if it is absent, which is why a logging procedure never has to check.

5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc log_line {path text} {
    set fp [open $path a]
    puts $fp $text
    close $fp
}

6. r+. It is usually not the right answer because modifying a file in place is harder to get right than reading it, changing the data in memory, and writing a fresh file — and a failure part-way through an in-place edit leaves the file in a state that is neither the old one nor the new one.

9. Remember

  • w starts from empty; a adds to the end. Both create the file if absent.
  • w truncates at open, not at the first write. A path that opens with w and writes nothing still empties the file.
  • So every early return and every error after an open ... w destroys the previous contents — open only once you know you have something to write.
  • a creates the file too, so a logging procedure never needs an existence check.
  • Ask whether the previous contents are yours to destroy. A generated report, yes; a shared log, no.
  • The per-call open/append/close shape is the standard logger — slower, and the data is on disk after every line.
  • r+ opens an existing file for writing without truncating, and in-place editing is usually worse than writing a new file and renaming it.

Where this fits

Part of the Tcl curriculum.