2015-11-20 14 views
5

Ich möchte eine Datei öffnen und ihren Inhalt als BufReader mit lines() lesen. Ich möchte auch in der Lage sein, bis zum Ende der Datei zu suchen und einige neue Zeilen zu schreiben.Wie benutzt man eine Datei mit einem BufReader und kann trotzdem darauf schreiben?

Mit let mut file lässt mich in die Datei schreiben, aber wenn ich die Datei in den BufReader gegeben habe kann ich nicht mehr schreiben, um es, wie die Hauptfunktion ist nicht länger Eigentümer 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). 
} 

Wie kann Ich benutze einen einzigen Dateideskriptor, um vorhandene Zeilen zu lesen und neue Zeilen anzufügen?

(Code aus https://github.com/chrisdew/tt/blob/e899f252014391f2e01c3cc9e281cab1ab88936f/src/main.rs)

Antwort

7

zu vermeiden Um einen Wert zu bewegen, können Sie einen Verweis und einen neuen Bereich verwenden. Hier ist, wie Sie dies tun könnten:

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

Sie BufReader::into_inner verwenden können, um „wiederherstellen“, um die Datei, nachdem er die BufReader weitergegeben worden ist. Dies kann in Verbindung mit Read::by_ref verwendet werden, um zu vermeiden dem Eigentum an den BufReader<File> in erster Linie verlosen:

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(); 
} 

Hier ist antoyo's solution mit ähnlichem reduziertem Code:

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(); 
} 

Wenn nicht-lexikalischer Lebensdauer (NLL) implementiert sind, kann dies durch Entfernen der zusätzlichen Klammern vereinfacht werden.