Skip to content

13 — IO, files and the system

One line: Ruby talks to the outside world through a few Kernel methods with strong conventions — puts, gets, File.open with a block, system, ARGV, ENV, exit — and each convention has a Python twin that differs in a detail worth measuring: what closes a file, what a newline means, what a missing key returns, what a child process hands back.

This chapter is the part of Ruby that touches the operating system: files and directories, the three standard streams, the command line and the environment, other programs, the serialisation formats a script reads and writes, time, and the formatting that turns values into output. Each lesson runs its Ruby program and its Python twin under the same conditions — in a temporary directory that the program removes, with child processes started from RbConfig.ruby and sys.executable, with TZ pinned to UTC and no Time.now — so that every row is the same on Ubuntu and on macOS, and a claim about "what Ruby prints" is a recorded fact rather than a memory.

For a Python programmer the shapes carry over almost one to one: File.open { } is with open(), puts is print, gets is input() or sys.stdin.readline(), ARGV is sys.argv[1:], ENV is os.environ, system and Open3.capture3 are subprocess.run, Pathname is pathlib.Path, JSON is json, Marshal is pickle, Time and Date are datetime and date, format is the % operator and "#{}" is an f-string. Where the model breaks is in the return values and defaults: gets returns nil at end of input where input() raises; puts nil prints an empty line where print(None) prints the word; system returns true/false/nil where subprocess.run returns an object; File.write counts bytes where write_text counts characters; Ruby keeps the \r of a CRLF file where Python's text mode translates it; JSON.parse gives String keys even when you wrote Symbols; YAML.load is safe by default where pickle.loads never is; Date >> 1 steps a month where date has no such step; format("%.2f", 2.675) rounds up where Python rounds down. Text — encodings, Unicode, regex, literals — is not repeated here: the last lesson is a map into the Ruby text library.

Lesson Level The one thing
File.open with a block closes the file for you 101 the block form closes on the way out and returns the block's value; bytes versus characters, and CRLF kept versus translated
puts adds a newline, print does not, p shows inspect 101 the four writers captured character by character, beside print with sep and end, and pprint's sorted keys
ARGV holds strings and ENV is not a Hash 101 ARGV, $0, ENV, exit and OptionParser measured from a parent process; exit false is 1 but sys.exit(False) is 0
system returns true, false or nil 201 six ways to run a program and what each hands back; the argv form skips the shell; a piped stdout is buffered in both languages
gets returns nil at EOF, and reads ARGV's files first 201 gets is ARGF.gets; nil at end of input where input() raises EOFError; fileinput is ARGF
Pathname#/ joins, and Dir.glob is sorted 201 Pathname beside pathlib.Path, Dir.glob sorted since 3.0 beside an unordered glob.glob, File.join versus os.path.join
JSON.parse gives string keys; YAML.load is safe 201 what JSON loses, what YAML.load refuses, what Marshal keeps; pickle has no safe mode and tomllib has no Ruby counterpart
Time is an instant, Date is a calendar day 201 fixed instants under TZ=UTC: seconds arithmetic, offsets, Date >> 1 clamping, Rational day differences; aware versus naive in Python
#{} calls to_s; format is printf 101 interpolation, format, String#%, named fields, padding by characters; f-strings, !r, and the thousands separator Ruby lacks
Text lives in the Ruby text library 101 one type plus a label versus str and bytes, then a map of all twenty-three text-library lessons beside their Python pages

Read more