Skip to content

File I/O

Overview

Frankie has a complete file I/O library built on Python's standard open() — zero external dependencies.

All functions are available without any import.


Reading Files

file_read(path)

Read entire file as a string

content = file_read("data.txt")
puts content

file_lines(path)

Read file as a vector of lines

Strips trailing newlines from each line.

lines = file_lines("data.csv")
puts lines.length         number of lines
puts lines[0]             first line
lines.each do |line|
  puts line
end

Both raise FileNotFoundError if the file doesn't exist — use rescue to handle it gracefully:

begin
  content = file_read("config.json")
rescue FileNotFoundError e
  puts "Config missing — using defaults"
  content = "{}"
end

Writing Files

file_write(path, content)

Write string to file (overwrites)

Creates the file if it doesn't exist. Overwrites silently if it does.

file_write("output.txt", "Hello, Frankie!\n")
file_write("report.csv", "name,score\nAlice,95\nBob,87\n")

file_append(path, content)

Append to file

Adds content to the end of the file without overwriting.

file_write("log.txt", "Session started\n")
file_append("log.txt", "User logged in\n")
file_append("log.txt", "User logged out\n")

puts file_read("log.txt")

 Session started
 User logged in
 User logged out

File Management

file_exists(path)

Check if a file exists

Returns true or false. Never raises.

if file_exists("config.json")
  config = json_read("config.json")
else
  config = {host: "localhost", port: 3000}
end

file_delete(path)

Delete a file

Returns false if the file doesn't exist — deleting a missing file is a no-op, not an error.

file_delete("temp.txt")           true if deleted, false if missing
file_delete("/tmp/scratch.json")  safe to call even if missing

file_copy(src, dst)

Copy a file

Returns the destination path on success. Raises FileNotFoundError if source is missing.

file_copy("data.csv", "data.csv.bak")

file_rename(src, dst)

Rename or move a file

Raises FileNotFoundError if source is missing.

file_rename("draft.txt", "final.txt")
file_rename("output.csv", "/tmp/output.csv")    move to different directory

Directory Operations

file_mkdir(path)

Create a directory

Creates all intermediate directories automatically (like mkdir -p).

file_mkdir("data/reports/2024")    creates all three levels

dir_exists(path)

Check if a directory exists

if not dir_exists("output")
  file_mkdir("output")
end

dir_list(path)

List directory contents

Returns a sorted vector of filenames (not full paths). Defaults to current directory.

entries = dir_list("data")
puts entries    ["config.json", "users.csv", "report.txt"]

# List current directory
puts dir_list()

File Handles

For fine-grained control, open a file as a handle with file_open:

f = file_open("output.txt", "w")
f.write("line one\n")
f.write("line two\n")
f.close

Modes: "r" (read), "w" (write/overwrite), "a" (append).

Always close the handle when done. Use ensure to guarantee it:

f = file_open("data.txt", "r")
begin
  content = f.read
  puts content
ensure
  f.close
end

Consistent Error Behaviour

Function Missing file behaviour
file_read(path) Raises FileNotFoundError
file_lines(path) Raises FileNotFoundError
file_copy(src, dst) Raises FileNotFoundError (src must exist)
file_rename(src, dst) Raises FileNotFoundError (src must exist)
file_delete(path) Returns false — idempotent
file_exists(path) Returns false — that's what it tests
dir_exists(path) Returns false — same

Quick Reference

Function Description
file_read(path) Read entire file as string
file_lines(path) Read file as vector of lines
file_write(path, str) Write string to file (overwrites)
file_append(path, str) Append string to file
file_exists(path) True if file exists
file_delete(path) Delete file — returns false if missing
file_copy(src, dst) Copy file
file_rename(src, dst) Rename or move file
file_open(path, mode) Open file handle ("r", "w", "a")
file_mkdir(path) Create directory (and parents)
dir_exists(path) True if directory exists
dir_list(path) Sorted vector of filenames in directory
path_join(*parts) Join path segments with the OS separator
path_dirname(path) Parent directory of a path
path_basename(path) Filename component of a path
path_extname(path) File extension including the dot
path_stem(path) Filename without the extension
path_absolute(path) Resolve to an absolute path

Path Helpers (introduced in v1.15)

Pure path manipulation — no filesystem access required.

path_join(*parts)

Join one or more path segments with the OS separator. Handles trailing slashes correctly.

puts path_join("home", "alice", "docs")       # home/alice/docs
puts path_join("/usr", "local", "bin")        # /usr/local/bin

path_dirname(path)

Return the parent directory of the given path.

puts path_dirname("/home/alice/report.txt")   # /home/alice
puts path_dirname("data/results.csv")         # data

path_basename(path)

Return the filename component (last segment) of the path.

puts path_basename("/home/alice/report.txt")  # report.txt
puts path_basename("data/results.csv")        # results.csv

path_extname(path)

Return the file extension including the leading dot. Returns an empty string if there is none.

puts path_extname("/home/alice/report.txt")   # .txt
puts path_extname("archive.tar.gz")           # .gz
puts path_extname("Makefile")                 # (empty string)

path_stem(path)

Return the filename without the extension.

puts path_stem("/home/alice/report.txt")      # report
puts path_stem("archive.tar.gz")              # archive.tar

path_absolute(path)

Resolve a relative path to an absolute path based on the current working directory.

puts path_absolute("./data/db.sqlite")        # /home/alice/myapp/data/db.sqlite
puts path_absolute("../config.json")          # /home/alice/config.json

Shell & Environment (introduced in v1.16)

shell(cmd)

Run an OS shell command and return a structured result hash. Uses subprocess internally — no shell injection surprises.

Field Type Description
stdout string Standard output from the command
stderr string Standard error from the command
exit_code integer Process exit code (0 = success)
ok boolean true when exit_code == 0
result = shell("git log --oneline -5")
if result["ok"]
  puts result["stdout"]
else
  puts "git failed (exit #{result["exit_code"]}): #{result["stderr"]}"
end

# Capture command output into a variable
branch = shell("git rev-parse --abbrev-ref HEAD")["stdout"].strip
puts "Current branch: #{branch}"

# Check for a program's existence
if shell("which python3")["ok"]
  puts "python3 is available"
end

dotenv(path = ".env")

Load a .env file and merge its key/value pairs into the process environment (accessible via env()). Returns a hash of the keys that were loaded.

# Load the default .env file
dotenv()

# Load a specific file
dotenv(".env.production")

# The loaded vars are now available via env()
puts env("DATABASE_URL")
puts env("SECRET_KEY")

The .env file format:

# .env — lines starting with # are comments, blank lines are ignored
DATABASE_URL=postgres://localhost/myapp
SECRET_KEY=changeme
PORT=3000
# Inspect what was loaded
loaded = dotenv(".env.staging")
puts "Loaded #{loaded.keys.length} variables"
loaded.each do |k, v|
  puts "  #{k}=#{v}"
end

Combined with shell():

dotenv()   # load .env first

result = shell("pg_dump #{env("DATABASE_URL")} > backup.sql")
if result["ok"]
  puts "Backup complete"
else
  puts "Backup failed: #{result["stderr"]}"
end