Read and Write Files
How to read from and write to files safely across multiple programming languages.
Overview
Reading and writing files is one of the most common I/O tasks: loading configuration, processing logs, exporting reports, or persisting state. Doing it safely means handling encoding correctly and always closing the file handle.
Files are the universal interface between programs and persistent storage. Whether you are saving user preferences, reading server logs, or generating a CSV export, the same principles apply: open the file, perform the operation, and ensure the resource is released even when errors occur. Modern runtimes provide high-level abstractions that handle buffering, encoding, and cleanup automatically, but understanding the underlying mechanics helps you debug performance issues and avoid data corruption.
This approach shows how to the idiomatic way to read and write text files in Python, JavaScript (Node.js), and Bash, plus how to stream large files without exhausting memory.
When to Use
Use this recipe when:
- Loading configuration or data files at startup. See Parse JSON for structured config files.
- Generating reports, exports, or logs for audit and analytics
- Processing text line by line (CSV, logs, fixtures)
- Persisting small amounts of state without a database
- Reading and writing JSON or YAML configuration files
- Streaming large log files without loading them entirely into memory
- Creating temporary files for intermediate processing in data pipelines. See Call REST API for downloading remote data.
Solution
Python
Python’s with statement creates a context manager that automatically closes the file, even if an exception is raised inside the block. Always specify encoding="utf-8" to avoid platform-dependent defaults.
# Write
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("Hello, file!\n")
# Read
with open("notes.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
JavaScript
Node.js provides a promise-based API under node:fs/promises that avoids blocking the event loop. This is essential for server applications that handle concurrent requests.
import { readFile, writeFile } from "node:fs/promises";
await writeFile("notes.txt", "Hello, file!\n", "utf-8");
const content = await readFile("notes.txt", "utf-8");
console.log(content);
Bash
Bash uses shell redirection for file operations. The > operator overwrites the target file, while >> appends. These are the fastest way to write small amounts of data from scripts.
# Write (overwrite) and append
echo "Hello, file!" > notes.txt
echo "Another line" >> notes.txt
# Read
cat notes.txt
Explanation
- Python uses the
withstatement (context manager) so the file is always closed, even on error. Theopen()function accepts a mode string:"r"for read,"w"for write (truncate),"a"for append, and"x"for exclusive creation. Always specifyencoding="utf-8". - JavaScript uses the promise-based
fs/promisesAPI. Prefer it over the synchronousreadFileSync/writeFileSync, which block the event loop. For large files, usecreateReadStream()to process data in chunks. - Bash uses redirection:
>overwrites,>>appends.catprints the contents. For structured parsing, combinecatwithjqfor JSON orawkfor CSV.
To turn file contents into structured data, see Parse JSON.
Variants
| Language | Read | Write | Append |
|---|---|---|---|
| Python | open(p).read() | open(p, "w") | open(p, "a") |
| JavaScript | readFile(p) | writeFile(p, data) | appendFile(p, data) |
| Bash | cat p | > p | >> p |
What Works
- Always set encoding: explicit
utf-8avoids platform-dependent defaults that can corrupt non-ASCII characters on Windows or macOS. - Use context managers / async APIs:
within Python,fs/promisesin Node, to avoid descriptor leaks and event-loop blocking. These abstractions guarantee cleanup even when exceptions occur. - Check the path exists: handle missing files gracefully rather than crashing. In Python, use
pathlib.Path.exists(); in Node, usefs.access()orfs.stat(). - Stream large files: read line by line instead of loading gigabytes into memory. Python provides
for line in f; Node providesreadlineorcreateReadStream; Bash provideswhile read line. - Write atomically: write to a temp file then rename, to avoid corrupting data on crash. If the process dies mid-write, the original file remains intact.
- Use absolute paths in scripts: relative paths break when the working directory changes. Resolve paths with
pathlib(Python) orpath.resolve()(Node) before opening files. - Set restrictive permissions on sensitive files: config files containing secrets should be readable only by the owner (
chmod 600).
Common Mistakes
- Forgetting to close the handle: leaks file descriptors and eventually exhausts the process limit; always use
withortry/finally. - Blocking the event loop in Node: avoid
readFileSyncin request handlers. A single synchronous read can freeze your entire server for all concurrent users. - Wrong encoding: reading UTF-8 as ASCII corrupts non-English characters and can produce mojibake in logs or user-facing output.
- Overwriting with
>: using>instead of>>in Bash silently erases the file with no undo or confirmation. - Ignoring errors: a missing file or permission error should be handled, not swallowed with an empty catch. Log the error and fail gracefully.
- Reading entire files into memory: loading a 10 GB log file into a string will crash your process. Always check the file size or use streaming for anything over a few megabytes.
- Writing to the same file you are reading: overwriting an input file in-place can truncate it before you finish reading, resulting in data loss.
Advanced Solutions
Python: Atomic writes with pathlib and error handling
import os
import tempfile
from pathlib import Path
from typing import Any
def safe_write(path: str | Path, data: str, encoding: str = 'utf-8') -> None:
"""Write text atomically: temp file + rename. Safe against crashes."""
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
fd, tmp_path = tempfile.mkstemp(
dir=path.parent, suffix='.tmp', prefix=path.name
)
try:
with os.fdopen(fd, 'w', encoding=encoding) as f:
f.write(data)
f.flush()
os.fsync(f.fileno())
os.replace(tmp_path, path)
except Exception:
Path(tmp_path).unlink(missing_ok=True)
raise
def safe_read(path: str | Path, encoding: str = 'utf-8',
default: str | None = None) -> str | None:
"""Read text with graceful fallback for missing files."""
path = Path(path)
if not path.exists():
return default
try:
return path.read_text(encoding=encoding)
except PermissionError:
raise PermissionError(f"Cannot read {path}: permission denied")
except UnicodeDecodeError as e:
raise UnicodeDecodeError(
e.encoding, e.object, e.start, e.end,
f"File {path} is not valid {encoding}"
)
def read_lines_lazy(path: str | Path, encoding: str = 'utf-8') -> list[str]:
"""Read file lines lazily, stripping whitespace from each line."""
path = Path(path)
with path.open('r', encoding=encoding) as f:
return [line.rstrip('\n\r') for line in f if line.strip()]
def write_json_atomic(path: str | Path, data: Any, indent: int = 2) -> None:
"""Serialize JSON and write atomically."""
import json
text = json.dumps(data, indent=indent, ensure_ascii=False, default=str)
safe_write(path, text)
# Usage
# safe_write('/etc/app/config.yaml', 'key: value\n')
# content = safe_read('/etc/app/config.yaml', default='key: default\n')
# write_json_atomic('/data/state.json', {'users': 42, 'active': 10})
Node.js: Streaming read/write with error recovery
const fs = require('fs');
const { pipeline } = require('stream');
const { promisify } = require('util');
const { createReadStream, createWriteStream } = fs;
const pipe = promisify(pipeline);
async function streamFile(srcPath, destPath, transformFn) {
const tmpPath = destPath + '.tmp';
const readStream = createReadStream(srcPath, { encoding: 'utf-8' });
const writeStream = createWriteStream(tmpPath, { encoding: 'utf-8' });
let lineBuffer = '';
const lineTransform = new (require('stream').Transform)({
transform(chunk, encoding, callback) {
lineBuffer += chunk;
const lines = lineBuffer.split('\n');
lineBuffer = lines.pop();
for (const line of lines) {
const result = transformFn(line);
if (result !== null) this.push(result + '\n');
}
callback();
},
flush(callback) {
if (lineBuffer) {
const result = transformFn(lineBuffer);
if (result !== null) this.push(result + '\n');
}
callback();
},
});
try {
await pipe(readStream, lineTransform, writeStream);
await fs.promises.rename(tmpPath, destPath);
} catch (err) {
try { await fs.promises.unlink(tmpPath); } catch {}
throw err;
}
}
async function readLines(path) {
const content = await fs.promises.readFile(path, 'utf-8');
return content.split('\n').filter(l => l.trim());
}
async function appendLine(path, line) {
await fs.promises.appendFile(path, line + '\n', 'utf-8');
}
// Usage
// streamFile('input.log', 'output.log', line => line.toUpperCase());
// const lines = await readLines('config.txt');
// await appendLine('app.log', `[${new Date().toISOString()}] Started`);
Java: NIO file operations with atomic writes
import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.ArrayList;
public class FileOps {
// Atomic write: temp file + Files.move with ATOMIC_MOVE
public static void atomicWrite(Path path, String content) throws IOException {
Path parent = path.getParent();
if (parent != null) Files.createDirectories(parent);
Path tmp = Files.createTempFile(parent, path.getFileName().toString(), ".tmp");
try {
Files.writeString(tmp, content, StandardCharsets.UTF_8);
Files.move(tmp, path, StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
Files.deleteIfExists(tmp);
throw e;
}
}
// Safe read with fallback
public static String safeRead(Path path, String defaultValue) {
if (!Files.exists(path)) return defaultValue;
try {
return Files.readString(path, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read " + path, e);
}
}
// Read all lines lazily
public static List<String> readLines(Path path) throws IOException {
return Files.readAllLines(path, StandardCharsets.UTF_8);
}
// Append a line
public static void appendLine(Path path, String line) throws IOException {
String entry = line + System.lineSeparator();
Files.writeString(path, entry,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
}
// Stream lines with try-with-resources
public static void processLines(Path path, LineHandler handler) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
handler.handle(line);
}
}
}
@FunctionalInterface
public interface LineHandler {
void handle(String line) throws IOException;
}
}
// Usage
// FileOps.atomicWrite(Path.of("/etc/app/config.yaml"), "key: value\n");
// String config = FileOps.safeRead(Path.of("config.yaml"), "key: default\n");
// FileOps.processLines(Path.of("large.log"), line -> {
// if (line.contains("ERROR")) System.err.println(line);
// });
Bash: Safe file operations with error checking
#!/usr/bin/env bash
set -euo pipefail
# Safe write: write to temp file, then atomically rename
safe_write() {
local file="$1"
local content="$2"
local tmp="${file}.tmp.$$"
local dir
dir="$(dirname "$file")"
mkdir -p "$dir"
printf '%s' "$content" > "$tmp"
mv "$tmp" "$file"
}
# Safe read: check existence first, provide default
safe_read() {
local file="$1"
local default="${2:-}"
if [[ -f "$file" && -r "$file" ]]; then
cat "$file"
else
printf '%s' "$default"
fi
}
# Append with timestamp (for logging)
log_append() {
local file="$1"
local message="$2"
local timestamp
timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
printf '[%s] %s\n' "$timestamp" "$message" >> "$file"
}
# Read file line by line with error handling
read_lines() {
local file="$1"
if [[ ! -f "$file" ]]; then
echo "Error: $file not found" >&2
return 1
fi
while IFS= read -r line || [[ -n "$line" ]]; do
echo "$line"
done < "$file"
}
# Create file with restrictive permissions (for secrets)
create_secret_file() {
local file="$1"
local content="$2"
# Create with 600 permissions directly
(umask 077; printf '%s' "$content" > "$file")
echo "Created $file with 600 permissions"
}
# Usage
# safe_write /etc/app/config.txt "key=value"
# content=$(safe_read /etc/app/config.txt "key=default")
# log_append /var/log/app.log "Application started"
# create_secret_file /etc/app/secret.key "my-secret-key-123" Frequently Asked Questions
How do I append instead of overwrite?
Open in append mode: open(p, "a") in Python, appendFile in Node, or >> in Bash. This preserves existing content and adds new data at the end.
Why should I avoid readFileSync in Node.js?
It blocks the single-threaded event loop, freezing all other requests until the read completes. Use fs/promises instead for any production server code.
How do I read a large file without running out of memory?
Stream it line by line — for line in f in Python, createReadStream in Node, or while read line in Bash. This keeps memory usage constant regardless of file size.
How do I safely write to a file that other processes might be reading?
Write to a temporary file on the same filesystem, then atomically rename it over the target. Readers will see either the old complete file or the new complete file, never a partially written one.
What is the difference between text and binary mode?
Text mode applies platform-specific newline translation (\r\n on Windows) and encoding. Binary mode reads raw bytes without transformation. Use binary mode for images, archives, or when you need exact byte-for-byte fidelity.
Related Resources
Call a REST API: Python, JavaScript, Java & Go Examples
How to make HTTP requests to a REST API and handle the JSON response in Python, JavaScript, Java, and Go.
RecipeParse JSON
How to parse JSON strings into native data structures across multiple programming languages.
RecipeRegular Expressions
How to use regular expressions for pattern matching, validation, and text extraction across Python, JavaScript, and Java.
RecipeBash Scripting for DevOps Automation and System Tasks
How to write reliable Bash scripts for automating deployments, system monitoring, log rotation, and routine maintenance tasks
RecipeExport Data to CSV and Excel Files
How to export structured data to CSV and Excel files efficiently.
RecipeFile Upload Validation
How to handle file uploads securely with size, type, and content validation.