2

I have a program where a user can write (append) to a chosen text file for storing information. Each line should be two numbers, and a date string (YYYY-MM-DD). There a multiple different text files that the program might write to, say "foo.txt" "bar.txt", "stroustrup.txt," and the file contents might look like this:

-18 30  2025-02-12
0   0   2025-02-01
-23 36  2025-02-27

Now I would like to add a sort of "undo" function, which deletes the last line from a chosen text file, in case the user input something erroneous, i.e. undo(const string& filename).

I know one possibility is to use getline(file,string) continuously, until reaching end of file, and writing everything but the last entry to a new file, thus creating a copy with the final line omitted. I feel like there should be a better way of doing this, however. As 1. I would have to create a new file, with a new name, for every call of my undo. And rewriting an entire file just to get rid of one line - one that is always at the very end even - seems wasteful.

Using seek() functions is also inappropiate, since the whole point is to delete erroneous input, I can't reasonably search for one particular string (since the user could input whatever silly string into the .txt file, before trying to undo).

Is there a neat way of doing this, i.e. replacing the final line with empty space or "\n", without having to copy and rewrite the entirety of the other contents?

8
  • 4
    One thing you could do is to keep everything in memory and only write at the very end. There are only two reasons not to do that: The files are too big (unlikely these days with text); or you want backup in case of a crash. The latter is easily fixed by writing the entire file periodically in the background. Commented Apr 3 at 20:24
  • 6
    seek() to the end of the file and skip backwards until you find the penultimate "\n". Once you have the offset, truncate is your friend. Commented Apr 3 at 20:25
  • 1
    And in general, file handling beyond open/close/write is beyond the reach of C, i.e., it is platform dependent; POSIX offers truncate functions, but if you don't have the file in memory (which allows backward search) you'll have to read the file and keep track of newlines until you hit EOF. You can probably seek + read blocks backwards and search those for the ultimate and penultimate newline ... all in all, pretty ugly. Commented Apr 3 at 20:30
  • You don't have to copy the file. You can simply getline() through the file, taking note of the tellg() position of the last line read until you get to the end. Then just truncate the file to the last tellg() position. Commented Apr 3 at 22:24
  • 1
    @Peter-ReinstateMonica std::filesystem::resize_file can truncate files as of C++17 Commented Apr 4 at 11:49

1 Answer 1

2

IMHO, the difficulty is identifying the file position where the last line begins. Not a simple task because files are designed to be appended to (for example, a binary search on a file is possible but not efficient).

I recommend having a std::vector<file-position-type>. For the entire file, use std::getline to read in a text line. Save the file position after the std::getline. This will produce a container of file positions of the beginning of each line.

After the container is populated, you can find the file position of the last line. Now, you can copy the contents of the file to a new file, stop when you reach the last file position. Or you can calculate the size of the original file, less the starting position of the last text line and "block copy" all those items to a new file.

If your application creates the data file, you can maintain the starting position of the last text line, so you don't need to construct the container again.

Edit 1: Remembering the last position

Rather than using a container, you can maintain the file position of the beginning of the last text line. Every time you read in a line, you update the previous file position. No container needed.

Edit 2: Fixed length text lines

If your text lines are fixed length, the location of the start of the last text line becomes a math calculation. Get the length of the file. Subtract the length of a text line. Done.

Sign up to request clarification or add additional context in comments.

4 Comments

I like this pragmatic approach. One could use a std::deque to store X amount of undo levels. Perhaps ignore() could be used instead of std::getline to skip ahead without the need for a std::string too.
Sadly I can't rely on e.g. fixed length. While the date format is always checked to be YYYY-MM-DD, the two preceeding numbers can be any real (representable by a float, any way). Point being it could be as little as one digit, or several more.
@Woodenplank Then the main idea in this answer should work, right?
Yes, thank you. It's still a rewriting of the whole file, essentially, but at least this sticks to a single file name and such. Also, it's just a .txt file with numbers (and hyphens), so it should be fast any way.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.