I/O And Filesystem
Import io for console output, file and directory operations, file handles,
temporary files, buffers, CSV convenience helpers, and stdin.
io owns filesystem mutation in Geblang. sys owns process and environment
state, while path owns path string manipulation. For example, use path.join
to build a path, io.mkdir to create it, and sys.tmpdir when you need the
host temporary directory.
import io;
import path;
import sys;
let work = path.join(sys.tmpdir(), "geb-work");
io.mkdir(work, 0o755);
Most io functions raise an error on OS failure: missing files, permission
errors, invalid handles, failed flushes, and unsupported operations are not
silently ignored. Use try/catch when absence or failure is expected.
Console Output
| Function | Returns | Description |
|---|---|---|
print(value) |
void |
Write a printable value without a newline |
println(value) |
void |
Write a printable value followed by a newline |
stdoutWrite(text) |
void |
Write a string directly to stdout |
stderrWrite(text) |
void |
Write a string directly to stderr |
stderrPrintln(value) |
void |
Write a printable value to stderr with a newline |
io.print("hello ");
io.println("world");
io.stdoutWrite("raw\n");
io.stderrWrite("warning\n");
io.stderrPrintln("fatal");
print and println accept any printable Geblang value. stdoutWrite and
stderrWrite expect strings and do not add formatting.
Whole-File Operations
Use whole-file helpers for small files that fit comfortably in memory.
| Function | Returns | Description |
|---|---|---|
readText(path) |
string |
Read a full file as text |
writeText(path, text) |
void |
Create or truncate a file and write text |
appendText(path, text) |
void |
Append text, creating the file if needed |
readBytes(path) |
bytes |
Read a full file as bytes |
writeBytes(path, data) |
void |
Create or truncate a file and write bytes |
appendBytes(path, data) |
void |
Append bytes, creating the file if needed |
io.writeText("notes.txt", "first line\n");
io.appendText("notes.txt", "second line\n");
let text = io.readText("notes.txt");
let data = io.readBytes("notes.txt");
For large files or files you want to stream incrementally, use file handles.
File Existence And Metadata
| Function | Returns | Description |
|---|---|---|
exists(path) |
bool |
true if the path exists |
stat(path) |
dict |
File metadata |
stat(path) returns:
| Key | Type | Description |
|---|---|---|
name |
string |
Base name |
size |
int |
Size in bytes |
isDir |
bool |
Whether the path is a directory |
mode |
int |
Permission bits, such as 0o644 |
modUnix |
int |
Last modification time as a Unix timestamp |
if (io.exists("app.log")) {
let info = io.stat("app.log");
io.println(info["size"] as string);
io.println(info["mode"] as string);
}
exists returns false for missing paths. stat raises an error if the path
cannot be inspected.
Directories
| Function | Returns | Description |
|---|---|---|
mkdir(path, mode) |
void |
Create a directory and missing parents |
remove(path) |
void |
Remove a file or directory tree |
rename(oldPath, newPath) |
void |
Move or rename a path |
listDir(path) |
list<string> |
Return child names in one directory |
walkDir(path) |
list<dict> |
Recursively return metadata for a directory tree |
let root = io.tempDir("geb-docs-*");
defer io.remove(root);
io.mkdir(root + "/nested/reports", 0o755);
io.writeText(root + "/nested/reports/today.txt", "ok");
for (name in io.listDir(root + "/nested")) {
io.println(name);
}
mkdir creates missing parents. The mode argument is the requested permission
bits, such as 0o755. The host operating system may still apply its normal
umask.
remove removes files and directories recursively. Use it carefully when a path
comes from user input.
walkDir(path) returns one dictionary per file or directory:
| Key | Type | Description |
|---|---|---|
path |
string |
Full walked path |
name |
string |
Base name |
isDir |
bool |
Whether the entry is a directory |
size |
int |
Size in bytes |
modUnix |
int |
Last modification time as a Unix timestamp |
for (entry in io.walkDir("src")) {
if (!(entry["isDir"] as bool) && entry["name"].endsWith(".gb")) {
io.println(entry["path"]);
}
}
Permissions, Ownership, And Links
| Function | Returns | Description |
|---|---|---|
chmod(path, mode) |
void |
Change permission bits |
chown(path, uid, gid) |
void |
Change numeric owner and group |
symlink(target, linkPath) |
void |
Create a symbolic link |
readLink(linkPath) |
string |
Read a symbolic link target |
io.writeText("secret.txt", "token");
io.chmod("secret.txt", 0o600);
io.symlink("secret.txt", "latest-secret.txt");
io.println(io.readLink("latest-secret.txt"));
chmod and chown are OS-level operations. On some platforms, especially
Windows, permission semantics may differ from Unix-style mode bits. chown
normally requires appropriate OS privileges.
Temporary Files And Directories
| Function | Returns | Description |
|---|---|---|
tempFile(pattern) |
string |
Create a temporary file and return its path |
tempDir(pattern) |
string |
Create a temporary directory and return its path |
Use * in the pattern as the random suffix placeholder.
let file = io.tempFile("geb-example-*.txt");
defer io.remove(file);
io.writeText(file, "temporary data");
io.println(io.readText(file));
let dir = io.tempDir("geb-work-*");
defer io.remove(dir);
io.mkdir(dir + "/cache", 0o755);
Temporary helpers create the path immediately. They do not automatically clean
up; use defer io.remove(path) when the file or directory is short-lived.
File Handles
File handles are integer handles owned by the runtime. Open a handle when you need incremental reads/writes, flushing, file locking, or sync control.
| Function | Returns | Description |
|---|---|---|
open(path, mode) |
int |
Open a file handle |
close(handle) |
void |
Close a file handle |
read(handle, n) |
string |
Read up to n bytes |
readAll(handle) |
string |
Read all remaining data |
readLines(handle) |
list<string> |
Read all remaining lines |
write(handle, text) |
int |
Write text; returns bytes written |
writeln(handle, text) |
int |
Write text and newline |
flush(handle) |
void |
Flush buffered data |
sync(handle) |
void |
Flush file data and metadata to storage |
dataSync(handle) |
void |
Flush file data to storage |
Supported open modes:
| Mode | Meaning |
|---|---|
"r" |
Read-only |
"w" |
Write, create, truncate |
"a" |
Append, create if needed |
"r+" |
Read and write |
let f = io.open("app.log", "a");
defer io.close(f);
io.writeln(f, "started");
io.flush(f);
Reading incrementally:
let f = io.open("app.log", "r");
defer io.close(f);
let chunk = io.read(f, 4096);
while (chunk != "") {
io.print(chunk);
chunk = io.read(f, 4096);
}
Always close file handles. defer io.close(handle) is the normal pattern.
Streams And Capture
Use stream constructors when you want file-like resources that are not opened
from filesystem paths. io.open(path, mode) remains filesystem-only; stdout,
stderr, stdin, and memory streams are created explicitly.
| Function | Returns | Description |
|---|---|---|
memory() |
IOStream |
Create an empty in-memory read/write stream |
memory(initial) |
IOStream |
Create a memory stream from a string or bytes value |
stdout() |
IOStream |
Open the current evaluator stdout as a write stream |
stderr() |
IOStream |
Open the current evaluator stderr as a write stream |
stdin() |
IOStream |
Open the current evaluator stdin as a read stream |
toString(stream) |
string |
Return memory-backed stream contents |
captureStdout() |
IOCapture |
Redirect stdout to a memory capture |
captureStderr() |
IOCapture |
Redirect stderr to a memory capture |
redirectStdout(stream) |
callable<void> |
Redirect stdout to a writable stream until the returned restore function is called |
redirectStderr(stream) |
callable<void> |
Redirect stderr to a writable stream until restored |
redirectStdin(stream) |
callable<void> |
Redirect stdin to a readable stream until restored |
Streams work with the same read, readBytes, readAll, write, writeBytes,
writeln, flush, and close functions used by file handles, where the
operation is supported by the stream.
let mem = io.memory("hello");
io.writeln(mem, " streams");
io.println(io.toString(mem)); # hello streams
Capture helpers are evaluator-local. They are designed for tests and framework code that needs to inspect output without writing to the real console:
let capture = io.captureStdout();
io.println("hidden from real stdout");
io.stdoutWrite("also captured\n");
let text = io.toString(capture);
io.close(capture);
io.println(text.contains("hidden"));
Use defer with redirect helpers when a test temporarily replaces a stream:
let mem = io.memory();
let restore = io.redirectStdout(mem);
io.println("captured in memory");
restore();
io.println(io.toString(mem));
Calling io.close(capture) restores the previous stdout or stderr target. Use
io.toString(stream) to inspect memory-backed streams and captures. Use
io.read(stream, n), io.readAll(stream), and io.readBytes(stream, n) when
you want to consume readable stream content.
File Locking
| Function | Returns | Description |
|---|---|---|
lock(handle) |
void |
Acquire an exclusive advisory lock, blocking if needed |
tryLock(handle) |
bool |
Attempt an exclusive advisory lock without blocking |
unlock(handle) |
void |
Release a lock |
let f = io.open("state.db", "r+");
defer io.close(f);
io.lock(f);
defer io.unlock(f);
# critical section
Locks are advisory and process-level. Other processes must also cooperate with advisory locking for it to protect shared files.
Buffers
Buffers are in-memory write targets. They are useful when an API accepts a file or buffer handle, or when you want to build text incrementally.
| Function | Returns | Description |
|---|---|---|
buffer() |
IOBuffer |
Create an in-memory buffer |
bufferToString(buffer) |
string |
Return buffer contents as text |
bufferReset(buffer) |
void |
Clear buffer contents |
write and writeln accept buffers as well as file handles:
let buf = io.buffer();
io.write(buf, "hello ");
io.writeln(buf, "world");
io.println(io.bufferToString(buf));
io.bufferReset(buf);
Stdin Helpers
| Function | Returns | Description |
|---|---|---|
stdinReadLine() |
`string | null` |
stdinReadAll() |
string |
Read all of stdin |
readLine(prompt) |
`string | null` |
let name = io.readLine("Name: ");
if (name != null) {
io.println("Hello " + name);
}
For masked input and richer terminal UI, use the cli module.
CSV Convenience Helpers
These helpers read or write small CSV files in memory.
| Function | Returns | Description |
|---|---|---|
readCSV(path) |
list<list<string>> |
Read a whole CSV file |
writeCSV(path, rows) |
void |
Write rows to a CSV file |
let rows = io.readCSV("users.csv");
rows = rows.push(["3", "Linus"]);
io.writeCSV("users.csv", rows);
For large CSV files, use the streaming APIs in the csv module.
Common Patterns
Create a temporary workspace:
let root = io.tempDir("job-*");
defer io.remove(root);
let out = root + "/out.txt";
io.writeText(out, "result");
Safely update a file:
let tmp = io.tempFile("settings-*.tmp");
io.writeText(tmp, newText);
io.chmod(tmp, 0o600);
io.rename(tmp, "settings.conf");
Lock a file while processing:
func processLog(string path): void {
let f = io.open(path, "r");
defer io.close(f);
io.lock(f);
defer io.unlock(f);
let content = io.readAll(f);
# process content
}