6

I'm using Visual C++ Express 2010... and I'm very new to C++.

I want to read a file then remove everything before the word "<--START-->" and rewrite the file with the rest.

Here's the code I've got for reading the file so far:

#include "stdafx.h"
#include <iostream>
#include <fstream>

using namespace std;

int main() {
  ifstream myReadFile;
  myReadFile.open("text.txt");
  char output[500];
  int found;
  int line;
  if (myReadFile.is_open()) {
    line = 0;
 while (!myReadFile.eof()) {
     myReadFile >> output;
     if(line > 20) {
         cout << output;
     }
     line++;
 }
}
myReadFile.close();
system("PAUSE");
return 0;
}

Many thanks in advance.

DugoutSoccer
  • 422
  • 5
  • 19

5 Answers5

7

First of all, your while loop is wrong. In fact, such while loop is almost always wrong.

You should be writing the loop as:

while (myReadFile >> output) 
{
     if (line > 20) {
         cout << output;
     }
     line++;
}

Your while(!myReadFile.eof()) loop is wrong, because the eof flag (or any other failure flag) is set after an attempt to read from the stream fails; that means, if the attempt to read fails, you're still outputting, because you're still inside the loop, and the rest of the code in the loop still executes when it in fact should not.

In my version, however, if an attempt to read (i.e myReadFile >> output) fails, then returned std::istream& implicitly converts into false, and the loop exits immediately. And if it doesn't fail, the returned stream implicitly converts to true.

By the way, it seems to me that you want to read line-by-line, instead of word-by-word. If so, then you should write this as:

std::string sline; //this should be std::string
while (std::getline(myReadFile, sline))
{
     if (line > 20) {
         cout << sline;
     }
     line++;
}

Again std::getline returns std::istream. If the read was successful, the returned stream implicitly converts to true and the loop will continue, or if it was unsuccessful, then it would implicitly convert to false and the loop will exit.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
2
std::string file_contents = LoadFileAsString("text.txt");
std::string::size_type offset = file_contents.find("<--START-->");
std::ofstream("text.txt") << file_contents.c_str() + offset;

With LoadFileAsString defined like this:

std::string LoadFileAsString(const std::string & fn)
{
    std::ifstream fin(fn.c_str());

    if(!fin)
    {
        std::string what = "LoadFileAsString() : Failed to open file \"";
        what += fn + '\"';
        throw std::runtime_error(what);
    }

    std::ostringstream oss;
    oss << fin.rdbuf();

    return oss.str();
}
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • @Zack: It only fails for values of huge that are quite uncharacteristic of text files. – Benjamin Lindley Jun 19 '11 at 19:59
  • I've been slinging multi-gigabyte packet trace files a lot lately, so my idea of "typical" is perhaps a little skewed right now. (Yeah, those aren't text, but it's a mindset thing.) – zwol Jun 19 '11 at 20:55
1

Here's a version that doesn't have to read the whole file into memory. Note: uses C stdio, not iostreams (just because I know C stdio a lot better); reads from standard input, writes to standard output.

#include <stdio.h>

int
main(void)
{
    int c;
    enum { BOF, LT, D1, D2, S, T1, A, R, T2, D3, D4, GO } state = BOF;
    while ((c = getchar()) != EOF)
        switch (state)
        {
        case BOF: state = c == '<' ? LT : BOF; break;
        case LT:  state = c == '-' ? D1 : c == '<' ? LT : BOF; break;
        case D1:  state = c == '-' ? D2 : c == '<' ? LT : BOF; break;
        case D2:  state = c == 'S' ? S  : c == '<' ? LT : BOF; break;
        case S:   state = c == 'T' ? T1 : c == '<' ? LT : BOF; break;
        case T1:  state = c == 'A' ? A  : c == '<' ? LT : BOF; break;
        case A:   state = c == 'R' ? R  : c == '<' ? LT : BOF; break;
        case R:   state = c == 'T' ? T2 : c == '<' ? LT : BOF; break;
        case T2:  state = c == '-' ? D3 : c == '<' ? LT : BOF; break;
        case D3:  state = c == '-' ? D4 : c == '<' ? LT : BOF; break;
        case D4:  state = c == '>' ? GO : c == '<' ? LT : BOF; break;
        case GO:  putchar(c); break;
        }
    return 0;
}
zwol
  • 135,547
  • 38
  • 252
  • 361
  • +1, I like the concept -- requires just 1 byte of working memory and does an optimal amount of work -- but it has a bug: when you encounter a mismatch, you need to drop back to the state matching the next-longest prefix that could possibly match the chars read so far. In particular that means that reading a `<` in any state besides `BOF` should drop you back to `LT` instead of `BOF`. (For other target strings the transitions could be more complicated -- specifically this occurs when a proper prefix of the string occurs elsewhere in the string. I suggest googling KMP string search.) – j_random_hacker Jun 19 '11 at 20:38
  • Actually because the target string here is "simple" in the sense that every mismatch takes you back to either the 1st or 2nd state, your state machine is basically "linear", and you could simplify it down to `char needle[] = "<--START-->"; int state = 0; while ((c = getchar()) != EOF) { if (needle[state]) { if (c == needle[state]) state++; else state = c == needle[0]; } else putchar(c); }` – j_random_hacker Jun 19 '11 at 20:41
  • You're quite right. I'm leaving my spelled-out state machine (with the error corrected) because I think it's more educational that way, but I like your "linear" simplification. – zwol Jun 19 '11 at 20:57
0

You cannot read a file then remove everything before the word "<--START-->" and rewrite the file with the rest, except in memory as answered by Benjamin. Otherwise you need an intermediate file. In all cases you should handle various error conditions. This should do it:

#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>

using namespace std;

int main() 
{   
    if (rename("Text.txt", "Old.txt") == 0)
    {
        try
        {
            ifstream in("Old.txt");
            ofstream out("Text.txt");
            string line;
            while (getline(in, line))
            {
                size_t pos = line.find("<--START-->");
                if (pos != string::npos)
                {
                    string remain = line.substr(pos + 11);
                    if (remain.size())
                        out << remain << endl;
                    break;
                }
            }
            while (getline(in, line))
                out << line << endl;
        }
        catch (exception&)
        {
            remove("Text.txt");
            rename("Old.txt", "Text.txt");
            cerr << "Error processing file" << endl;
            return 1;
        }
        remove("Old.txt");
        return 0; 
    }
    cerr << "Error renaming file, access right?" << endl;
    return 2; 
} 

.

Alain Rist
  • 827
  • 6
  • 11
0

Well, there are a lot of answers here already, but here's another. It's very short because it's super simple, it's one-pass, perfectly portable, and it doesn't allocate anything on the heap.

#include <iterator>
#include <istream>
#include <ostream>

void strip_file_beginning( std::istream &in_s, std::ostream &out_s ) {
    std::istreambuf_iterator< char > in( in_s ), in_end;
    std::ostreambuf_iterator< char > out( out_s );
    static char const start_word[] = "<--START-->"; // mind the terminating '\0'

    for ( char const *pen = start_word; pen != start_word + sizeof start_word - 1
                                  && in != in_end; ++ in ) {
        if ( * in == * pen ) ++ pen;
        else pen = start_word;
    }

    std::copy( in, in_end, out );
}

http://ideone.com/zh9bd

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421