如何在文件开头添加一行?

7
我正在尝试在文本文件开头添加一个新行。我首先使用append打开文件,但这只允许我使用write_all写入到文件的末尾,至少这是我得到的结果。如果我正确地阅读了文档,那么这是有意设计的。
我尝试使用seek进行调整,但这并没有解决问题。
以下是我目前的代码:
let mut file = OpenOptions::new().append(true).open(&file_path).unwrap();
file.seek(SeekFrom::Start(0));
file.write_all(b"Cool days\n");

如果我使用 write 打开文件,那么我会覆盖数据而不是添加。在 Rust 中,应该如何完成这个操作呢?


如果你在文件的开头添加一行,那么这将需要移动文件剩余内容以适应它。在任何编程语言中都无法避免这种情况。请参见C#中的此示例 - Peter Hall
2个回答

19

您无法直接在任何编程语言中实现此功能。请参阅有关C#PythonNodeJsPHPBashC的其他相关问题。

有几种解决方案,具有不同的权衡:

  1. 将整个文件复制到内存中,写入所需数据,然后在其后写入剩余部分。如果文件很大,这可能是一个不好的解决方案,因为它将使用大量内存,但对于小文件来说,它非常简单易行。
  2. 使用缓冲区,大小与要预置的文本相同。一次将文件的块复制到内存中,然后用前一个块覆盖它。通过这种方式,您可以将文件内容向前移动,并将新文本放在开头。这可能比其他方法慢,但不需要大量内存分配。当进程没有删除文件的权限时,这也可能是最佳选择。但要小心:如果进程被中断,此方法可能会使文件处于损坏状态。
  3. 将新数据写入临时文件,然后附加原始内容。然后删除原始文件并重命名临时文件。这是一个好的解决方案,因为它将繁重的工作委托给操作系统,并且原始数据已备份,因此如果进程被中断,它将不会损坏。

从 Stack Overflow 的搜索结果来看,第三个解决方案似乎是其他语言中最受欢迎的答案,例如 Bash。这很可能是因为它快速、安全,而且通常只需要几行代码就可以实现。

一个快速的 Rust 版本大致如下:

extern crate mktemp;
use mktemp::Temp;
use std::{fs, io, io::Write, fs::File, path::Path};

fn prepend_file<P: AsRef<Path>>(data: &[u8], file_path: &P) -> io::Result<()> {
    // Create a temporary file 
    let mut tmp_path = Temp::new_file()?;
    // Stop the temp file being automatically deleted when the variable
    // is dropped, by releasing it.
    tmp_path.release();
    // Open temp file for writing
    let mut tmp = File::create(&tmp_path)?;
    // Open source file for reading
    let mut src = File::open(&file_path)?;
    // Write the data to prepend
    tmp.write_all(&data)?;
    // Copy the rest of the source file
    io::copy(&mut src, &mut tmp)?;
    fs::remove_file(&file_path)?;
    fs::rename(&tmp_path, &file_path)?;
    Ok(())
}

使用方法:

fn main() -> io::Result<()> {
    let file_path = Path::new("file.txt");
    let data = "Data to add to the beginning of the file\n";
    prepend_file(data.as_bytes(), &file_path)?;
    Ok(())
}

谢谢,这回答了我的问题。我想象过那样的解决方案,但我觉得这就像是重复造轮子,但显然这个轮子确实需要重新制造。顺便说一下,这将成为一个很棒的库。 - pedronveloso

0

一个包含4个步骤的示例:(Peter的解决方案#1)

  • 创建一个包含要预先添加的数据的vec
  • 打开文件,读取内容并附加到vec中
  • 创建具有相同路径的文件,这将截断(请参阅文档File :: create
  • 写入内容
use std::fs::File;
use std::path::Path;
use std::io::{Read, Write, Result};

fn prepend_file<P: AsRef<Path> + ?Sized>(data: &[u8], path: &P) -> Result<()> {
    let mut f =  File::open(path)?;
    let mut content = data.to_owned();
    f.read_to_end(&mut content)?;

    let mut f = File::create(path)?;
    f.write_all(content.as_slice())?;

    Ok(())
}

fn main() -> Result<()> {
    prepend_file("hello world\n".as_bytes(), "/tmp/file.txt")
}


Peter的解决方案非常完美,但它会带来一个额外的箱子。处理小文件时最好避免使用它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接