Write/append newline to file?

Hello everyone. Is there a straight forward way to append a new line to a file?
I use:

std.fs.cwd().openFile

to open a file in read/write mode.

Then I for loop over a list of type - std.ArrayList([]const u8)

for (file_names.items) |name| {
     defer allocator.free(name);
      _ = try file.writeAll(name);
      _ = try file.writeAll('\n'); // can't do that
 }

You must seek to the end of the file using seekFromEnd:

    try file.seekFromEnd(0);
1 Like

Create the file, with no truncation,

std.fs.cwd().createFile(filename, .{ .truncate = false });

Then seek to the end before writing,

try file.seekFromEnd(0);
2 Likes

Thank you for both of your answers. I am still not sure how that API is supposed to work.
I have tried to do:

1.
_ = try file.seekFromEnd(0);

    for (file_names.items) |name| {
        defer allocator.free(name);
        _ = try file.writeAll(name);
    }
2.
    for (file_names.items) |name| {
        defer allocator.free(name);
        _ = try file.writeAll(name);
        _ = try file.seekFromEnd(0);
    }
3.
    for (file_names.items) |name| {
        defer allocator.free(name);
        _ = try file.seekFromEnd(0);
        _ = try file.writeAll(name);
    }

None of which works. What I am missing?

I think you should post your program, because currently I don’t understand why you would defer allocator.free(name);, showing your program (or at least a bigger portion for more context) instead of just pieces, may explain better why.

I would have expected something like this:

_ = try file.seekFromEnd(0);
for (file_names.items) |name| {
    _ = try file.writeAll(name);
    _ = try file.writeAll("\n");
}

And I would expect other code to take care of deallocating file_names, for example with a defer statement.

That doesn’t work because '\n' is a single character, but writeAll expects a slice so you need to use "\n", because that gives you a string literal that is automatically coerced to a slice.

1 Like

I want to note that if you want to atomically append to a file, then you will have to reach down into the native API for whatever OS you want to use (e g std.posix).

This is a known issue, though it is unclear if the File API will ever get an atomic append mode.

2 Likes

Looks like the API expects from me to move the offset. That kind of works now:

           var offset: i64 = 0;
            for (file_names.items) |name| {
                defer allocator.free(name);
                _ = try file.seekFromEnd(offset);
                _ = try file.writeAll(name);
                offset += @intCast(name.len);
            }

Wow that actually works if it’s a slice as you suggested _ = try file.writeAll("\n");, so it’s just me being dumb.

I think you should post your program, because currently I don’t understand why you would defer allocator.free(name); , showing your program (or at least a bigger portion for more context) instead of just pieces, may explain better why.

I am trying to implement the ‘ls’ cmd in Zig:

fn listDir(allocator: std.mem.Allocator, path: []const u8, _: anytype) !std.ArrayList([]const u8) {
    var file_names = std.ArrayList([]const u8).init(allocator);

    const dir_path = if (path.len == 0) "." else path;
    var dir = std.fs.cwd().openDir(dir_path, .{ .iterate = true }) catch |e| switch (e) {
        error.NotDir => {
            const copied_name = try allocator.alloc(u8, path.len);
            @memcpy(copied_name, path);
            try file_names.append(copied_name);
            return file_names;
        },
        else => return e,
    };
    defer dir.close();

    var dir_iterator = dir.iterate();
    while (try dir_iterator.next()) |dir_content| {
        const name = dir_content.name;
        const copied_name = try allocator.alloc(u8, name.len);
        @memcpy(copied_name, name);
        try file_names.append(copied_name);
    }

    return file_names;
}

That code is part of zig version - 0.14.0-dev.1569+b56a667ec

That’s the list I iterate on a later stage in my program. I am not sure how to free the memory in a better way. I am creating copy of these strings, because as per the doc message says -

Memory such as file names referenced in this returned entry becomes invalid with subsequent calls to next

Oh, yeah. I would like to also being able to sort that ArrayList, so just using

u8

instead

const u8

, won’t do the trick.

 std.mem.sort([]const u8, file_names.items, {}, struct {
      fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool {
         return std.mem.order(u8, lhs, rhs) == .lt;
     }
}.lessThan);

In the function where you use file_names and are done with it after the function is finished, I would declare a defer statement like this:

defer {
    for(file_names.items) |n| allocator.free(n);
    file_names.deinit();
}