Reading and Writing Files
os.WriteFile(path, data, perm) is the function everyone reaches for first,
and it does exactly what it says: write these bytes to this path. What the
name doesn't tell you is that it also truncates — call it twice on the
same path and the second call doesn't add to the file, it erases the first
call's contents and replaces them. That's correct for "save the current
state of this config," and a bug for "append a line to this log." Two
different jobs, and os.WriteFile only does one of them.
Opening with intent
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("open %s: %w", path, err)
}
defer f.Close()
os.OpenFile is the general form, and the flags are where you say what you
actually mean: O_CREATE if it might not exist yet, O_WRONLY because
you're only writing, and O_APPEND — the one that matters here — which
tells the OS to seek to the end before every write, atomically, so this call
never clobbers what a previous call (or another process) already wrote.
os.WriteFile under the hood is O_CREATE|O_WRONLY|O_TRUNC; swap O_TRUNC
for O_APPEND and you have the log-file version instead of the
save-file version. Same underlying syscall, opposite behavior, one flag.
0644 is the file's permission bits if it's created: owner can read and
write, everyone else can only read. It's ignored if the file already
exists — OpenFile doesn't re-chmod an existing file to match.
Building the path
path := filepath.Join(dir, name)
Not dir + "/" + name. filepath.Join uses the OS's actual separator (/
on Linux and macOS, \ on Windows), and it cleans the result — collapsing a
stray double slash if dir happened to end in one. String concatenation
does neither; it happens to work on the platform you're testing on, which
is exactly the kind of bug that survives code review and fails on a
teammate's machine or in a differently-configured container.
Why this matters more than it looks like it should
A log file, an audit trail, a append-only event feed — the moment a program
restarts and calls its "write to disk" function again, O_TRUNC versus
O_APPEND is the difference between "the log has everything" and "the log
has only what happened since the last restart," silently. Nothing about
os.WriteFile warns you; it does precisely what its one job is, and the bug
is entirely in reaching for the convenience function when the job actually
needed the general one.
Try it
Implement AppendLine(dir, name, line string) error. It must add line (as
its own line) to the end of the file at filepath.Join(dir, name), creating
the file if it doesn't exist, and never disturbing lines a previous call
already wrote.
Practice
1. Append a Line to a File
IntermediateImplement
AppendLine(dir, name, line string) error.- Build the file path by joining
dirandname. - Create the file if it doesn't already exist.
- Add
linefollowed by a newline to the end of the file — a previous call's lines must still be there afterward, in order. - Return a wrapped error if the file can't be opened or written to.
package main import "fmt" // AppendLine adds line to the end of the file at dir/name, creating it if // it doesn't exist, without disturbing lines already there. func AppendLine(dir, name, line string) error { // TODO: implement return fmt.Errorf("not implemented") } func main() {}Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
- Build the file path by joining