skip to content

In Ruby's File.open, what do the modes r, w, a and r+ do, and what does adding b to a mode change?

level: juniorimportance: should knowfreq 58%

answer

  1. r is the default
  2. w truncates, then writes
  3. a writes only at the end
  4. r+ needs an existing file
  5. b: ASCII-8BIT, no CRLF translation

basics

~20 s

Mode r, the default, reads an existing file; w creates or truncates and writes; a creates if needed and always writes at the end; r+ reads and writes an existing file without truncating. Adding b makes the stream binary.

solid answer

~40 s

`"r"` is the default: read-only, and the file must exist or `Errno::ENOENT` is raised; writing to it raises `IOError` (not opened for writing). `"w"` creates the file or **truncates** it to zero bytes, then writes. `"a"` creates if needed and sends every write to the end, even after `seek` or `rewind`. `"r+"` reads and writes an existing file from byte 0 without truncating, so writes overwrite in place. The `+` variants `"w+"` and `"a+"` add reading. Suffixing `b` (`"rb"`, `"wb"`) or calling `binmode` marks the data as binary: strings come back as ASCII-8BIT, and on Windows newline conversion is switched off. `x` after a write mode (`"wx"`) creates the file exclusively and raises `Errno::EEXIST` if it already exists.

code

ruby · 15 lines
ruby
File.write("notes.txt", "one\n")          # => 4, truncates first
File.open("notes.txt", "a") { |f| f.puts "two" }
File.read("notes.txt")                     # => "one\ntwo\n"

File.open("notes.txt", "r+") { |f| f.write("ONE") }
File.read("notes.txt")                     # => "ONE\ntwo\n"

File.open("notes.txt", "w") { }            # truncates to zero bytes
File.size("notes.txt")                     # => 0

logo = File.binread("logo.png")            # opened as "rb"
logo.encoding == Encoding::BINARY          # => true

# File.open("notes.txt") { |f| f.write("x") }  raises IOError (not opened for writing)
# File.open("missing.txt", "r+")              raises Errno::ENOENT

go deeper

for a junior

Recall the four core modes: r reads, w truncates and writes, a appends, r+ reads and writes without truncating; and that r is the default.

for a middle

Explain which modes create or require the file, why writes in a ignore seek, what IOError wrong-direction access raises, and what b and binmode change.

for a senior

Pick modes that cannot destroy data by accident, use wx for create-if-absent instead of an existence check, and insist on binary mode for anything hashed or copied byte for byte.

for a principal

Treat file modes as part of an API's safety contract: default writers to append or exclusive create, and make truncation an explicit, reviewed choice.

## What a mode string is `File.open(path, mode)` and `File.new(path, mode)` take a **mode string** built from up to three parts, documented in the `File` class rdoc (`file.c`, "Access Modes"): 1. A 1- or 2-character **read/write mode**: `r`, `w`, `a`, `r+`, `w+`, `a+`. 2. An optional **data mode**: `t` (text) or `b` (binary). 3. An optional **file-create mode**: `x`, only after a writable mode. When no mode is given, `File.open` uses `"r"`. The data mode and create flag cannot stand alone: `File.new(path, "b")` raises, and the order is fixed (`"rxb"` raises; `"wbx"` is legal). ## The read/write modes For an **existing** file, the rdoc's table reads: | Mode | Truncates? | Read | Write | Starting position | |---|---|---|---|---| | `r` | no | anywhere | error | 0 | | `w` | **yes** | error | anywhere | 0 | | `a` | no | error | end only | end | | `r+` | no | anywhere | anywhere | 0 | | `w+` | **yes** | anywhere | anywhere | 0 | | `a+` | no | anywhere | end only | end | Things interviewers probe from this table: - **`w` destroys content.** Opening a file with `"w"` truncates it immediately, before you write a byte. Opening a log in `"w"` to add a line wipes the log. - **`a` means end only.** In append mode every write goes to the end of the file; `IO#seek`, `IO#pos=` and `IO#rewind` move the *read* position (in `a+`) but never redirect a write. - **`r+` overwrites in place.** It starts at byte 0 and does not truncate, so writing `"ONE"` into `"one\ntwo\n"` yields `"ONE\ntwo\n"`; a shorter write leaves old bytes after it. - **`r` and `r+` require the file to exist.** On a missing path they raise `Errno::ENOENT`; `w`, `w+`, `a` and `a+` create it. - **Wrong-direction I/O is an error.** Writing to an `"r"` stream raises `IOError` ("not opened for writing"); reading from a `"w"` stream raises `IOError` ("not opened for reading"). ## The data mode: `b` and `binmode` Without a data mode a stream is **text**. Adding `b` changes two things: - The data is treated as raw bytes: strings read from the stream are tagged `ASCII-8BIT` (also called `BINARY`) instead of the default external encoding. This applies on every platform. - On Windows, the CRLF-to-LF conversion and the treatment of byte `0x1A` as end-of-file are switched off. On Linux and macOS there is no newline conversion in either mode. You get the same effect by calling `IO#binmode` on an open stream (it returns the stream, and a binary stream cannot be switched back to text), by passing `binmode: true` as an open option, or by using the class methods `File.binread` and `File.binwrite`. Use binary mode for images, archives, PDFs and anything you hash or checksum byte for byte. ## The create flag: `x` `"wx"` (or `"wbx"`) creates a new file and raises `Errno::EEXIST` if one is already there. The existence check and the creation happen in one system call, which makes it the right tool for lock files and "write only if absent" logic. ## Integer flags The mode can also be an **integer** built from constants ORed together, mirroring the operating system's `open` flags: `File::RDONLY`, `File::WRONLY`, `File::RDWR`, plus `File::CREAT`, `File::EXCL`, `File::TRUNC` and `File::APPEND`. So `"a"` corresponds to `File::WRONLY | File::CREAT | File::APPEND`, and `"wx"` to `File::WRONLY | File::CREAT | File::EXCL`. You meet the integer form in code ported from C or when a flag has no letter; string modes are the everyday spelling and read more clearly in review. ## Class-method shorthands `File.write(path, data)` opens the file for writing and, without an offset argument, **truncates** it first; it returns the number of bytes written. To append, pass the open option `mode:`: `File.write(path, line, mode: "a")`. `File.read(path)` opens with `"r"`. ## Common mistakes - Opening with `"w"` to update part of a file and losing everything else — that is `"r+"`'s job. - Expecting `seek` to position a write in an `"a"` stream. - Reading a PNG with plain `"r"` and getting a string in the wrong encoding, or on Windows a truncated or altered copy. - Treating `b` as Windows-only; it changes the string encoding everywhere.

  • In Ruby, why does calling rewind before a write on a file opened with "a" not overwrite the start of the file?
    In append mode every write goes to the end of the file. `rewind`, `seek` and `pos=` move the read position (useful in `"a+"`), but the operating system still appends each write. To overwrite existing bytes, open with `"r+"` and seek to the offset.
  • When would you use "wx" instead of checking whether the file exists first?
    When the file must be created only if it is absent, such as a lock or marker file. `"wx"` does the existence check and the creation in one open call and raises `Errno::EEXIST` if the file is already there, so two processes cannot both believe they created it.
  • Why can copying a PNG with File.read and File.write corrupt it on Windows but not on Linux?
    Plain `"r"` and `"w"` are text mode. On Windows, text mode converts line endings and treats byte `0x1A` as end-of-file, altering binary data; Linux does no conversion. Use `File.binread` and `File.binwrite`, or `"rb"`/`"wb"`, so the bytes pass through untouched and the string is tagged ASCII-8BIT.

saying these in an interview costs you the question

  • Mode w appends to whatever the file already contains.
  • Mode r+ creates the file when it does not exist.
  • In mode a you can seek back and overwrite earlier bytes.
  • The b flag only matters on Windows, so it is safe to leave out.
  • Writing to a file opened with the default mode silently does nothing.