4

I have a ./file.txt that is:

Lorem ipsum
dolor sit
amet

I want the same ./file.txt to be:

dolor sit
amet

I don't care about the file in any other way; I'm not going to be doing any further manipulation on it or anything like that. I only want to strip the first line.


std::fs::write overrides the entire content of a file. I asssume I would need to read the entire file into a String, remove everything from the start of the string until I find a new line (somehow), then override the entire file with that new string. Though maybe it's just me, all of that feels a bit overkill. I do not care for the entire contents of the file.

The reason I'm asking is because I simply do not know how to do this, especially so if there's a better way of going about this whole thing. If there is an obvious simple answer that'd be great.

This question was answered for Rust 1.0, which is rather outdated, to the point where BufReader.lines() no longer exists.

Gremious
  • 304
  • 3
  • 10
  • I don't see any errors with the code you've posted. – Shepmaster Jul 06 '20 at 18:26
  • 1
    Sharing your research helps everyone. Tell us what you've tried and why it didn’t meet your needs. This demonstrates that you’ve taken the time to try to help yourself, it saves us from reiterating obvious answers, and most of all it helps you get a more specific and relevant answer. See also: [ask] – John Kugelman Jul 06 '20 at 18:26
  • @Shepmaster I didn't post any code??? – Gremious Jul 06 '20 at 18:32
  • Your question might be answered by the answers of [How can I read a file line-by-line, eliminate duplicates, then write back to the same file?](https://stackoverflow.com/q/27871299/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Jul 06 '20 at 18:40
  • Sure, sec, I'll update the question better – Gremious Jul 06 '20 at 18:42
  • 1
    Shepmaster's first comment was a cheeky way of saying, "What have you tried?" – John Kugelman Jul 06 '20 at 18:55
  • *where `BufReader.lines()` no longer exists* — that is not correct. Rust's compatibility guarantees expressly forbid such a removal without **extremely** good reasons. – Shepmaster Jul 06 '20 at 19:15
  • My appologies, I jumped the gun a bit when getting a `no method called lines()` compiler error. Furtehred by the exmaple in the docs not calling BufReader explicitly but using `Cursor.`, and having to `use BufRead` alongside `use BufReader`(?). – Gremious Jul 06 '20 at 19:24

2 Answers2

3

Did more research, this is the closest I ended up getting.

I don't know if this is optimal, and am not entirely certain this preserves the formatting of the file (though I think it does). I think probably also, there's a better way of loading the file. I don't know many things.

But it works?

use std::{fs, fs::File, fs::OpenOptions};
use std::io::{BufReader, BufRead, Write};

fn main() {
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .open("./file.txt")
        .expect("file.txt doesn't exist or so");

    let mut lines = BufReader::new(file).lines().skip(1)
        .map(|x| x.unwrap())
        .collect::<Vec<String>>().join("\n");

    fs::write("./file.txt", lines).expect("Can't write");
}
Gremious
  • 304
  • 3
  • 10
1

You have to read everything but the first line and then override the contents of the file. I'd do it like this:

use std::{
    fs::File,
    io::{self, prelude::*},
};

fn remove_first_line(path: &str) -> io::Result<()> {
    let buf = {
        let r = File::open(path)?;
        let mut reader = io::BufReader::new(r);
        reader.read_until(b'\n', &mut Vec::new())?;
        let mut buf = Vec::new();
        reader.read_to_end(&mut buf)?;
        buf
    };
    File::create(path)?.write_all(&buf)?;
    Ok(())
}

I create a BufReader to read the first line, then I return whatever is left in the file and put it in once again.

If you don't want to close and open the file again, you have to change the seek position so you don't append data to the file:

use std::{
    fs::OpenOptions,
    io::{self, prelude::*},
};

fn remove_first_line(path: &str) -> io::Result<()> {
    let file = OpenOptions::new().read(true).write(true).open(path)?;
    let mut reader = io::BufReader::new(file);
    reader.read_until(b'\n', &mut Vec::new())?;
    let mut buf = Vec::new();
    reader.read_to_end(&mut buf)?;
    let mut file = reader.into_inner();
    file.seek(io::SeekFrom::Start(0))?;
    file.write_all(&buf)?;
    Ok(())
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Seems expensive to read the entire file into memory for this. – Shepmaster Jul 06 '20 at 19:14
  • I can't think of any other way to do this though, if I were to use something like `std::io::copy` the file would get overwritten and not the whole contents would be preserved. – nrabulinski Jul 06 '20 at 19:18