2015-11-20 30 views
5

我想打開一個文件並使用lines()將其內容作爲BufReader讀取。我也希望能夠找到文件的末尾並寫一些新的行。如何與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

回答

7

爲了避免移動值,你可以使用一個參考和一個新的範圍。 這裏是你如何能做到這一點:

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). 
} 
1

您可以使用BufReader::into_inner它被傳遞到BufReader後的「恢復」文件。這可以結合使用Read::by_ref,以避免在第一地點放棄的BufReader<File>的所有權:

use std::fs::File; 
use std::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; 
use std::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(); 
} 

非詞法壽命 (NLL)都可以實現,這可以通過刪除額外的大括號來簡化。

相關問題