【问题标题】:How to use a file with a BufReader and still be able to write to it?如何使用带有 BufReader 的文件并且仍然能够写入它?
【发布时间】:2016-02-23 05:04:19
【问题描述】:

我想使用lines() 打开一个文件并以BufReader 的形式读取其内容。我还希望能够找到文件末尾并写一些新行。

使用let mut file 让我可以写入文件,但是一旦我将文件提供给BufReader,我就无法再写入它,因为主函数不再拥有file

fn main() {
    let filename = "tt.txt";

    // open a tt.txt file in the local directory
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(filename)
        .unwrap();

    // now read the whole file to get the latest state
    let date_re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})").unwrap();
    let time_activity_re = Regex::new(r"^(\d{2}):(\d{2})\s*(.*)").unwrap();
    let reader = BufReader::new(file);
    let mut latest_date: Option<Date<Local>> = None;
    let mut latest_datetime: Option<DateTime<Local>> = None;
    let mut latest_activity: Option<String> = None;

    for wrapped_line in reader.lines() {
        let line = wrapped_line.unwrap();
        println!("line: {}", line);

        if date_re.is_match(&line) {
            let captures = date_re.captures(&line).unwrap();
            let year = captures.at(1).unwrap().parse::<i32>().unwrap();
            let month = captures.at(2).unwrap().parse::<u32>().unwrap();
            let day = captures.at(3).unwrap().parse::<u32>().unwrap();
            latest_date = Some(Local.ymd(year, month, day));
            latest_datetime = None;
            latest_activity = None;
        }

        if time_activity_re.is_match(&line) && latest_date != None {
            let captures = time_activity_re.captures(&line).unwrap();
            let hour = captures.at(1).unwrap().parse::<u32>().unwrap();
            let minute = captures.at(2).unwrap().parse::<u32>().unwrap();
            let activity = captures.at(3).unwrap();

            latest_datetime = Some(latest_date.unwrap().and_hms(hour, minute, 0));

            latest_activity = if activity.len() > 0 {
                // TODO: if latest_activity already constains a string, clear it and reuse it
                // as per: https://stackoverflow.com/questions/33781625/how-to-allocate-a-string-before-you-know-how-big-it-needs-to-be
                Some(activity.to_string())
            } else {
                None
            };

            println!("time activity: {} |{}|", latest_datetime.unwrap(), activity);
        }
    }

    // FIXME: I have to open a second file descriptor to the same file, in order to be able to write to it
    let mut out = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(filename)
        .unwrap();

    out.seek(End(0));

    let now = Local::now();
    if latest_date == None || latest_date.unwrap().year() != now.year()
        || latest_date.unwrap().month() != now.month()
        || latest_date.unwrap().day() != now.day()
    {
        if (latest_date != None) {
            // not an empy file, as far as tt is concerned
            out.write_all(b"\n\n");
        }
        out.write_all(format!("{}\n", now.format("%Y-%m-%d")).as_bytes());
        out.write_all(b"\n");
    }

    let activity = env::args().skip(1).join(" ");
    if (activity.len() > 0) {
        out.write_all(format!("{} {}\n", now.format("%H:%M"), activity).as_bytes());
    } else {
        // if there was no latest activity *and* there is no activity, then there's no point in writing a second blank line with just a time
        if latest_activity == None {
            return;
        }
        out.write_all(format!("{}\n", now.format("%H:%M")).as_bytes());
    }

    // FIXME: we're just relying on the program exit to close the two file descriptors (which point at the same file).
}

如何使用单个文件描述符来读取现有行并追加新行?

(来自https://github.com/chrisdew/tt/blob/e899f252014391f2e01c3cc9e281cab1ab88936f/src/main.rs的代码)

【问题讨论】:

    标签: file rust ownership


    【解决方案1】:

    在将文件传递给BufReader 后,您可以使用BufReader::into_inner“恢复”文件。这可以与Read::by_ref 结合使用,以避免首先放弃BufReader&lt;File&gt; 的所有权:

    use std::{
        fs::File,
        io::{BufRead, BufReader, Read, Write},
    };
    
    fn example(file: File) {
        let mut reader = BufReader::new(file);
        for _ in reader.by_ref().lines() {}
    
        let mut out = reader.into_inner();
    
        out.write_all(b"new stuff").unwrap();
    }
    

    这里是antoyo's solution 类似的简化代码:

    use std::{
        fs::File,
        io::{BufRead, BufReader, Write},
    };
    
    fn example(mut file: File) {
        let reader = BufReader::new(&file);
        for _ in reader.lines() {}
        file.write_all(b"new stuff").unwrap();
    }
    

    【讨论】:

      【解决方案2】:

      为避免移动值,您可以使用引用和新范围。 您可以这样做:

      fn main() {
          let filename = "tt.txt";
      
          // open a tt.txt file in the local directory
          let mut file = OpenOptions::new()
              .read(true)
              .write(true)
              .create(true)
              .open(filename)
              .unwrap();
      
          // now read the whole file to get the latest state
          let date_re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})").unwrap();
          let time_activity_re = Regex::new(r"^(\d{2}):(\d{2})\s*(.*)").unwrap();
          {
              // BufReader now borrows the value instead of taking ownership.
              let reader = BufReader::new(&mut file);
              let mut latest_date: Option<Date<Local>> = None;
              let mut latest_datetime: Option<DateTime<Local>> = None;
              let mut latest_activity: Option<String> = None;
      
              for wrapped_line in reader.lines() {
                  let line = wrapped_line.unwrap();
                  println!("line: {}", line);
      
                  if date_re.is_match(&line) {
                      let captures = date_re.captures(&line).unwrap();
                      let year = captures.at(1).unwrap().parse::<i32>().unwrap();
                      let month = captures.at(2).unwrap().parse::<u32>().unwrap();
                      let day = captures.at(3).unwrap().parse::<u32>().unwrap();
                      latest_date = Some(Local.ymd(year, month, day));
                      latest_datetime = None;
                      latest_activity = None;
                  }
      
                  if time_activity_re.is_match(&line) && latest_date != None {
                      let captures = time_activity_re.captures(&line).unwrap();
                      let hour = captures.at(1).unwrap().parse::<u32>().unwrap();
                      let minute = captures.at(2).unwrap().parse::<u32>().unwrap();
                      let activity = captures.at(3).unwrap();
      
                      latest_datetime = Some(latest_date.unwrap().and_hms(hour, minute, 0));
      
                      latest_activity = if activity.len() > 0 {
                          // TODO: if latest_activity already constains a string, clear it and reuse it
                          // as per: https://stackoverflow.com/questions/33781625/how-to-allocate-a-string-before-you-know-how-big-it-needs-to-be
                          Some(activity.to_string())
                      } else {
                          None
                      };
      
                      println!("time activity: {} |{}|", latest_datetime.unwrap(), activity);
                  }
              }
          }
          // End of the scope, so now file is not borrowed anymore.
      
          file.seek(End(0));
      
          let now = Local::now();
          if latest_date == None || latest_date.unwrap().year() != now.year()
              || latest_date.unwrap().month() != now.month()
              || latest_date.unwrap().day() != now.day()
          {
              if (latest_date != None) {
                  // not an empy file, as far as tt is concerned
                  file.write_all(b"\n\n");
              }
              file.write_all(format!("{}\n", now.format("%Y-%m-%d")).as_bytes());
              file.write_all(b"\n");
          }
      
          let activity = env::args().skip(1).join(" ");
          if (activity.len() > 0) {
              file.write_all(format!("{} {}\n", now.format("%H:%M"), activity).as_bytes());
          } else {
              // if there was no latest activity *and* there is no activity, then there's no point in writing a second blank line with just a time
              if latest_activity == None {
                  return;
              }
              file.write_all(format!("{}\n", now.format("%H:%M")).as_bytes());
          }
      
          // FIXME: we're just relying on the program exit to close the two file descriptors (which point at the same file).
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-08-03
        • 1970-01-01
        • 1970-01-01
        • 2019-09-07
        • 2014-08-04
        • 2018-12-27
        • 2015-10-02
        相关资源
        最近更新 更多