I want to open a file and replace just one word in it. I can't seem to find any method of doing that except by creating a new file with the new text.
1) Copy all the text up to the start of the line before which you want to insert the new line from the old file to the new file. 2) Write your new text to the new file, with a newline character at the end. 3) Copy all the text up to the end of the file from the old file to the new file. 4) Close both files.
A file is a sequence of bytes. For performance reasons, a file almost always occupies one chunk of space on the hard drive. It is also easier to manipulate a contiguous file from programmer's point of view, because in this case a file can be thought of as a plain array; otherwise there should be some linked-list-like or tree-like data structure, which in 99% of cases would only make programming harder. Consequently, a file can easily be appended to, but inserting or deleting data in the middle is harder. This is usually done in five steps (for insertion; removal is very similar):
Replacing words of possibly different length in general involves either removal or insertion. For files of decent size the simplest way is to read the whole source file into memory, run a replace operation on it and dump the results back into the original file. This way items 2-4 will be done automatically for you by the library code for string operations. Here is an example program (it takes source word, replacement word and file path from command line arguments):
use std::env;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;
fn main() {
// Handle errors
run().unwrap();
}
fn run() -> Result<(), io::Error> {
// Extract words and file path from the command line args
let args: Vec<String> = env::args().skip(1).collect();
if args.len() != 3 {
println!("Wrong number of arguments");
return Ok(());
}
let word_from = &args[0];
// If the source word is empty then there is nothing to replace
if word_from.is_empty() { return Ok(()); }
let word_to = &args[1];
let file_name = &args[2];
let file_path = Path::new(&file_name);
// Open and read the file entirely
let mut src = File::open(&file_path)?;
let mut data = String::new();
src.read_to_string(&mut data)?;
drop(src); // Close the file early
// Run the replace operation in memory
let new_data = data.replace(&*word_from, &*word_to);
// Recreate the file and dump the processed contents to it
let mut dst = File::create(&file_path)?;
dst.write(new_data.as_bytes())?;
println!("done");
Ok(())
}
Note that creating a temporary file is still a good idea because writing a large chunk of data to a file is not an atomic operation, while renaming a file usually is. Hence if something goes wrong and you don't use a temporary file, your source file will likely be corrupted. If you do use a temporary file, then the source file will either be replaced entirely or not.
If your files are large (that is, several gigabytes and larger), streaming replace is probably a good idea. In this case you will need to read your file in chunks (down to 1 byte length, which may make it easier) and run the replace operation in these chunks, writing the result to the temporary file. After the whole source file is processed, the temporary file is moved over it. If you read in chunks larger than single byte, you will also need to handle the situation when your word is "split" between these chunks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With