Skip to content

Instantly share code, notes, and snippets.

@ant1fact
Last active July 18, 2024 15:14
Show Gist options
  • Save ant1fact/38faaa371ff3fd225c4bc320eda17d63 to your computer and use it in GitHub Desktop.
Save ant1fact/38faaa371ff3fd225c4bc320eda17d63 to your computer and use it in GitHub Desktop.
Get the last line of a text file in Zig
const std = @import("std");
/// Try to get the last line of a text file. The returned line will be trimmed of any new line characters.
/// If the file is empty, or if the file only contains new lines, the returned slice will be empty.
pub fn getTail(allocator: std.mem.Allocator, file: *std.fs.File) ![]const u8 {
var result = std.ArrayList(u8).init(allocator);
const step_size: i64 = 4096; // Same as default BufferedReader
var read_buffer: [step_size]u8 = undefined;
var reader = file.reader();
try file.seekFromEnd(0);
var iter_count: usize = 0;
outer: while (true) {
// Each iteration, expand the capacity of the ArrayList storing the result
iter_count += 1;
try result.ensureTotalCapacity(iter_count * step_size);
// Get the current cursor position
const pos = try file.getPos();
// If the position is at 0, there is no more data to read, stop iterating
if (pos == 0) break;
// While there is data to read, move the cursor back -step_size or -pos, whichever is smaller
const seek_amount: i64 = @min(@as(i64, @intCast(pos)), step_size);
try file.seekBy(-seek_amount);
// Save the current position so we can move the cursor back to it once we have read the current chunk of bytes
const pos_backup = try file.getPos();
// Prevent reading more data than the amount the cursor was moved backward by limiting the buffer to seek_amount
const buffer_len: usize = @intCast(seek_amount);
const bytes_read = try reader.read(read_buffer[0..buffer_len]);
// Reset the cursor to the previously stored position
try file.seekTo(pos_backup);
// Read characters from the read_buffer backwards until a new line is found
var i: usize = bytes_read;
inner: while (i > 0) {
i -= 1;
const byte = read_buffer[i];
// Check for new line
if (byte == '\n' or byte == '\r') {
// Continue looping if the result buffer is empty, i.e. the new line found was at the end of the file
if (result.items.len == 0) continue :inner;
// If a new line was found and the result buffer was not empty, stop looking for a new line
break :outer;
}
// Keep adding bytes to the result buffer
result.appendAssumeCapacity(byte);
}
}
// Since we've written the bytes into the ArrayList in reverse order, we need to reverse it back
std.mem.reverse(u8, result.items);
return try result.toOwnedSlice();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment