C++ reading and then editing a text file - c++

I am wondering how to read in and edit a text file by searching for lines containing foobar and then erasing those lines only. Don't need a full program, if someone could just point me towards the right fstream functions.

#include <iostream>
#include <algorithm>
#include <string>
class line {
std::string data;
public:
friend std::istream &operator>>(std::istream &is, line &l) {
std::getline(is, l.data);
return is;
}
operator std::string() const { return data; }
};
int main() {
std::remove_copy_if(std::istream_iterator<line>(std::cin),
std::istream_iterator<line>(),
std::ostream_iterator<std::string>(std::cout, "\n"),
[](std::string const &s) {
return s.find("foobar") != std::string::npos;
});
return 0;
}

do something like this:
string sLine = "";
infile.open("temp.txt");
while (getline(infile, sLine))
{
if (strstr(sLine, "foobar") != NULL)
cout<<sLine;
else
//you don't want this line... it contains foobar
}
infile.close();
cout << "Read file completed!!" << endl;
here I have printed the output to the console, not back to the file, because this should point you in the right direction.
if you need a hint on how to print the lines to the file read below:
Save all the lines that don't contain foobar to a string. After you have read the whole file, close it, then open it with write permission and write the string to it. this will also overwrite the old content.

Related

How to read/write to a file in C++?

I'm new to file read/write in c++. Please someone help me the best way to read the file somewhat like shown below ta class like this
class Student
{
public:
string fName;
string sName;
int mark
};
// file.txt each data is ends with newline and metadata ends with ';'
Firstname1;SecondName1;Mark1
Firstname2;SecondName2;Mark2
Firstname3;SecondName3;Mark3
Firstname4;SecondName4;Mark4
Firstname5;SecondName5;Mark5
please someone help me to find a best way
So, we have here 2 problems to solve.
read data from a file
split the read data into its parts
The format of the source text file is so called "CSV", for "Comma Separated Values". Instead of comma, any other meaningfull separator may be used.
So, what needs to be done?
Open the file and check, if it could be opened
In a loop read line by line from the file
split the values into its parts
store the parts in your struct
The challenge for you is here the "splitting" of the string. There are many many many potential solutions, let me share one often used approach with std::getline.
std::getline basically reads characters from an input stream and places them into a string. Reading is done until a delimiter is found. If you do not specifically provide a delimiter as input parameter, then '\n' is assumed. So, it will read a complete line from a stream (file).
Then, because many people do not understand the pitfalls in using a mix of formatted and unformatted input, the read line is put into a std::istringstream. This is also more robust/resilient against problems Then we can extract the parts of the string again with using std::getline.
One potential example could be:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
class Student
{
public:
std::string fName{};
std::string sName{};
int mark{};
};
int main() {
// Open file and check, if it could be opened
if (std::ifstream sourceFileStream{ "file.txt" }; sourceFileStream) {
// The file could be opened. Define a vector, where we store all Student data
std::vector<Student> students{};
// Read all lines of the file
for (std::string line; std::getline(sourceFileStream, line); ) {
// Put the line into an std::istringstream, to extract the parts
std::istringstream iss{ line };
// Define a temporary Student for storing the parts
Student tempStudent{};
// Get the parts
std::getline(iss, tempStudent.fName, ';');
std::getline(iss, tempStudent.sName, ';');
// Use formatted input function to get the mark and convert it to an int
iss >> tempStudent.mark;
// So, now all parts are in the tempStudent. Add this to the result
students.push_back(tempStudent);
}
// For debug purposes, we show all read data
for (const Student& student : students)
std::cout << student.fName << '\t' << student.sName << "\t Mark: " << student.mark << '\n';
}
else
// File could not be opened. Show error message
std::cerr << "\n\nError: 'file.txt' could not be opened\n";
}
For the more experienced users. In C++ we often use a more object oriented approach. We store the data and the methods, operating on that data in the class.
C++ allows us to override the extraction >> and inserter << operator. Those will then be part of the class and make IO easier.
Please see the little bit more advanced solution below:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <iterator>
class Student
{
public:
std::string fName{};
std::string sName{};
int mark{};
friend std::istream& operator >> (std::istream& is, Student& s) {
if (std::string line{}; std::getline(is, line)) {
std::istringstream iss{ line };
std::getline(std::getline(iss, s.fName, ';'), s.sName) >> s.mark;
}
return is;
}
friend std::ostream& operator << (std::ostream& os, const Student& s) {
return os << s.fName << '\t' << s.sName << "\t Mark: " << s.mark;
}
};
class Students
{
public:
std::vector<Student> data{};
friend std::istream& operator >> (std::istream& is, Students& s) {
s.data = { std::istream_iterator<Student>(is),{} };
return is;
}
friend std::ostream& operator << (std::ostream& os, const Students& s) {
for (const Student& d : s.data) os << d << '\n';
return os;
}
};
int main() {
// Open file and check, if it could be opened
if (std::ifstream sourceFileStream{ "file.txt" }; sourceFileStream) {
// The file could be opened. Define a instance of Students
Students students{};
// Read all students
sourceFileStream >> students;
// Show all data
std::cout << students;
}
else
// File could not be opened. SHow error message
std::cerr << "\n\nError: 'file.txt' could not be opened\n";
}

Reading custom file formats in C++

I read configuration files of the following format into my C++ code:
# name score
Marc 19.7
Alex 3.0
Julia 21.2
So far, I have adapted a solution found here: Parse (split) a string in C++ using string delimiter (standard C++). For example, the following code snippet reads in the file line by line, and for each line calls parseDictionaryLine, which discards the first line, splits the string as described in the original thread, and inserts the values into a (self-implemented) hash table.
void parseDictionaryLine(std::string &line, std::string &delimiter, hash_table &table) {
size_t position = 0;
std::string name;
float score;
while((position = line.find(delimiter)) != std::string::npos) {
name = line.substr(0, position);
line.erase(0, position + delimiter.length());
score = stof(line);
table.hinsert(name, score);
}
}
void loadDictionary(const std::string &path, hash_table &table) {
std::string line;
std::ifstream fin(path);
std::string delimiter = " ";
int lineNumber = 0;
if(fin.is_open()) {
while(getline(fin, line)) {
if(lineNumber++ < 1) {
continue; // first line
}
parseDictionaryLine(line, delimiter, table);
}
fin.close();
}
else {
std::cerr << "Unable to open file." << std::endl;
}
}
My question would be, is there a more elegant way in C++ to achieve this task? In particular, is there (1) a better split function as for example in Python, (2) a better method to test if a line is a comment line (starting with #), like startsWith (3) potentially even in iterator that handles files similar to a context manager in Python and makes sure the file will actually be closed? My solution works for simple cases shown here but becomes more clunky with more complicated variations such as several comment lines at unpredictable positions and more parameters. Also, it worries me that my solution does not check if the file actually agrees with the prescribed format (two values per line, first is string, second is float). Implementing these checks with my method seems very cumbersome.
I understand there is JSON and other file formats with libraries made for this use case, but I am dealing with legacy code and cannot go there.
I will try to answer all your questions.
First for splitting a string, you should not use the linked question/answer. It is from 2010 and rather outdated. Or, you need to scroll at the very bottom. There you will find more modern answers.
In C++ many things are done with iterators. Because a lot of algorithms or constructors in C++ work with iterators. So, the better approch for splitting a string is to use iterators. This will then always result in a one liner.
Background. A std::string is also a container. And you can iterate over elements like for example words or values in it. In case of space separated values you can use the std::istream_iterator on a std::istringstream. But since years there is a dedicated iterator for iterating of patterns in a string:
The std::sregex_token_iterator. And because it is specifically designed for that purpuse, it should be used.
Ans if it is used for splitting the strings, the overhead of using regexes is also minimal. So, you may split on strings, commas, colons or whatever. Example:
#include <iostream>
#include <string>
#include <vector>
#include <regex>
const std::regex re(";");
int main() {
// Some test string to be splitted
std::string test{ "Label;42;string;3.14" };
// Split and store whatever number of elements in the vector. One Liner
std::vector data(std::sregex_token_iterator(test.begin(), test.end(), re, -1), {});
// Some debug output
for (const std::string& s : data) std::cout << s << '\n';
}
So, regardless of the number of patterns, it will copy all data parts into the std::vector.
So, now you have a one liner solution for splitting strings.
For checking. if the first character is a string, you may use
the index operator (if (string[0] == '#'))
or, the std::string's front function (if (string.front() == '#'))
or again a regex
But, here you need to be careful. The string must not be empty, so, better write:
if (not string.empty() and string.front() == '#')
Closing file or iterating over files.
If you use a std::ifstream then the constructor will open the file for you and the destructor will automatically close it, when the stream variable rund out of scope. The typical pattern here is:
// Open the file and check, if it coud be opened
if (std::iftsream fileStream{"test.txt"};fileStream) {
// Do things
} // <-- This will close the file automatically for you
Then, in general you shoud use a more object oriented approach. Data, and methods operating on this data, should be encapsulated in one class. Then you would overwrite the extractor operatoe >> and the inserter operator << to read and write the data. This, because only the class should know, how to handle the data. And if you decide to use a different mechanism, modify your class and the rest of the outside world will still work.
In your example case, input and output is that simple, that easiest IO will work. No splitting of string necessary.
Please see the following example.
And note especially the only few statements in main.
If you change something inside the classes, it will simple continue to work.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
// Data in one line
struct Data {
// Name and score
std::string name{};
double score{};
// Extractor and inserter
friend std::istream& operator >> (std::istream& is, Data& d) { return is >> d.name >> d.score; }
friend std::ostream& operator << (std::ostream& os, const Data& d) { return os << d.name << '\t' << d.score; }
};
// Datbase, so all data from the source file
struct DataBase {
std::vector<Data> data{};
// Extractor
friend std::istream& operator >> (std::istream& is, DataBase& d) {
// Clear old data
d.data.clear(); Data element{};
// Read all lines from source stream
for (std::string line{}; std::getline(is, line);) {
// Ignore empty and command lines
if (not line.empty() and line.front() != '#') {
// Call extractor from Data class end get the data
std::istringstream(line) >> element;
// And save new data in the datbase
d.data.push_back(std::move(element));
}
}
return is;
}
// Inserter. Output all data
friend std::ostream& operator << (std::ostream& os, const DataBase& d) {
std::copy(d.data.begin(), d.data.end(), std::ostream_iterator<Data>(os, "\n"));
return os;
}
};
int main() {
// Open file and check, if it is open
if (std::ifstream ifs{ "test.txt" }; ifs) {
// Our database
DataBase db{};
// Read all data
ifs >> db;
// Debug output show all data
std::cout << db;
}
else std::cerr << "\nError: Could not open source file\n";
}
You can use operator>> to split at delimiters for you, like this:
#include <iostream>
#include <sstream>
#include <unordered_map>
std::istringstream input{
"# name score\n"
"Marc 19.7\n"
"Alex 3.0\n"
"Julia 21.2\n"
};
auto ReadDictionary(std::istream& stream)
{
// unordered_map has O(1) lookup, map has n(log n) lookup
// so I prefer unordered maps as dictionaries.
std::unordered_map<std::string, double> dictionary;
std::string header;
// read the first line from input (the comment line or header)
std::getline(stream, header);
std::string name;
std::string score;
// read name and score from line (>> will split at delimiters for you)
while (stream >> name >> score)
{
dictionary.insert({ name, std::stod(score) });
}
return dictionary;
}
int main()
{
auto dictionary = ReadDictionary(input); // todo replace with file stream
// range based for loop : https://en.cppreference.com/w/cpp/language/range-for
// captured binding : https://en.cppreference.com/w/cpp/language/structured_binding
for (const auto& [name, score] : dictionary)
{
std::cout << name << ": " << score << "\n";
}
return 0;
}

How to write multiple text files from a text file in C++?

I have a txt file that has 500,000 lines, and each line has 5 columns. I want to read data from this file and write it into different 5000 txt files that have 100 lines each, starting from the first line to the last of the input file. Also, the filename is output with the order number, say "1_Hello.txt", which has the 1st line to 100th line, "2_Hello.txt", which has the 101st line to 200th line, and so on, until "5000_Hello.txt", which has the 499901st line to 500000th line.
I used to run the following code to write files that are less than 10 files. But How can I write it in the case of 5000 text files? Any help would be appreciated.
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;
int main() {
vector<string> VecData;
string data;
ifstream in("mytext.txt");
while (in >> data) {
VecData.push_back(data);
}
in.close();
ofstream mynewfile1;
char filename[]="0_Hello.txt";
int i, k=0, l=0;
while(l<VecData.size()){
for(int j='1';j<='3';j++){
filename[0]=j;
mynewfile1.open(filename);
for( i=k; i<k+((int)VecData.size()/3);i+=5){
mynewfile1<<VecData[i]<<"\t";
mynewfile1<<VecData[i+1]<<"\t";
mynewfile1<<VecData[i+2]<<"\t";
mynewfile1<<VecData[i+3]<<"\t";
mynewfile1<<VecData[i+4]<<endl;
}
mynewfile1.close();
l=i;
k+=(int)VecData.size()/3;
}
}
cout<<"Done\n";
return 0;
}
You're working too hard – you don't need to read the entire input first, and you don't need to care about the structure of each line.
Read and write line-by-line, a hundred lines at a time.
Stop when there is nothing more to read.
Something like this should do it:
int main()
{
std::ifstream in("mytext.txt");
int index = 0;
std::string line;
while (in)
{
std::string name(std::to_string(index) + "_Hello.txt");
std::ofstream out(name);
for (int i = 0; i < 100 && std::getline(in, line); i++)
{
out << line << '\n';
}
index += 1;
}
cout << "Done\n";
}
You've already gotten an answer but I'll give an alternative that uses std::copy_n,std::istream_iterator and std::ostream_iterator.
This copies 100 lines at a time to the current output file.
I've added a class wrapping a std::string to be able to provide my own streaming operators for the string to make it read one line at a time.
#include <algorithm>
#include <fstream>
#include <iostream>
struct Line {
std::string str;
};
std::istream& operator>>(std::istream& is, Line& l) {
return std::getline(is, l.str);
}
std::ostream& operator<<(std::ostream& os, const Line& l) {
return os << l.str << '\n';
}
int main() {
if(std::ifstream in("mytext.txt"); in) {
for(unsigned count = 1;; ++count) {
// Constructing an istream_operator makes it read one value from the
// stream and if that fails, it'll set the eofbit on the stream.
std::istream_iterator<Line> in_it(in);
if(!in) break; // EOF - nothing more in the input file
// Open the output file and copy 100 lines into it:
if(std::ofstream out(std::to_string(count) + "_Hello.txt"); out) {
std::copy_n(in_it, 100, std::ostream_iterator<Line>(out));
}
}
}
}

Ignoring empty line during fstream

Writing a program to read a text file and storing it in a struct. An example of the text file:
chicken
dog
car
765
When there is some text in the line, it will get store into the struct. I have tried the following:
getline(file, aLine);
Info.animalchicken = aLine;
getline(file, aLine);
Info.animaldog = aLine;
getline(file, aLine);
Info.car = aLine;
getline(file, aLine);
Info.number = aLine;
I realised that the getline is literally getting every single line. When I run this in my program, the chicken will be stored in the struct Info.animalchicken. The next line, which is empty, will store into Info.animaldog. Dog will be stored in Info.car and so on.
I think a control loop is required here but can't think of a good one. How can I ignore the empty line so my text can enter into the struct correctly?
This is my struct
struct Info {
string animalchicken;
string animaldog;
string car;
int number;
}
The loop idea, while quite primitive, should do the trick; the easiest way would be to wrap the logic in a separate function:
std::string getlineFilterEmpty(std::istream& s) {
std::string line;
do {
if (!s) {
throw std::runtime_error("End of stream");
}
getline(s, line);
} while(line.size() == 0);
return line;
}
Then getting your values is as simple as:
Info.animalchicken = getlineFilterEmpty(file);
Info.animaldog = getlineFilterEmpty(file);
Info.car = getlineFilterEmpty(file);
The number member will require parsing the string to an integer, the code for which you'll find elsewhere on SO.
The logic needs to go something like,
Read a line.
If read succeeded
If line not empty
Provide line
Else
Try again
Else
Handle error
Translating that into code and bundling it into a function for easy reuse, we get
std::string getNotEmptyLine(std::istream & in)
{
while (true) // repeat forever!
{
std::string temp;
std::getline(in, temp); // get a line
if (in) // test the line
{
if (line.size() != 0) // line not empty
{
return temp; //give it to caller
}
}
else
{
// handle error. We'll throw an exception, but this isn't the best solution
throw std::runtime_error("Couldn't read a line!");
}
}
}
As with all literal translations, it needs a bit of work. It would also be helpful to make this function work exactly like getline so the caller can use it as a drop-in replacement.
std::istream & getNotEmptyLine(std::istream & in, // stream to read
std::string & line, // somewhere to put the string
char delim = '\n') // allow different delimiters
{
while (true) // repeat forever!
{
if (std::getline(in, line, delim)) // get a line right in line and test that we got it.
{
if (line.size() != 0) // line not empty
{
break; // success. exit.
}
}
else
{
// line will contain whatever this implementation of `getline` puts or
// leaves in the string on failure.
break; // fail. Let the caller decide what to do
}
}
return in;
}
Usage:
Info info;
std::string aLine;
if (getNotEmptyLine(in, info.animalchicken) &&
getNotEmptyLine(in, info.animaldog) &&
getNotEmptyLine(in, info.car) &&
getNotEmptyLine(in, aLine))
{
info.number = std::stoi(aLine);
}
else
{
// handle error
}
Note: even this may be too simplistic. It can't handle a line that contains nothing but whitespace. A single misplaced and nigh-invisible space will wreak havoc. If this is a concern, add more logic to if (line.size() != 0)
Here's an option adding stream operators and a helper function to skip empty lines.
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
struct Info {
std::string animalchicken;
std::string animaldog;
std::string car;
int number;
};
// a helper function to do getline but skip empty lines
std::istream& getline_with_content(std::istream& is, std::string& s) {
while(std::getline(is, s)) if(not s.empty()) break;
return is;
}
// an istream operator to read one Info
std::istream& operator>>(std::istream& is, Info& i) {
getline_with_content(
getline_with_content(
getline_with_content(is,
i.animalchicken),
i.animaldog),
i.car);
is >> i.number;
// ignore everything after the number until a newline appears:
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return is;
}
// an ostream operator to print one Info
std::ostream& operator<<(std::ostream& os, const Info& i) {
return os << i.animalchicken << '\n'
<< i.animaldog << '\n'
<< i.car << '\n'
<< i.number << '\n';
}
int main() {
// an example istream with a lot of blank lines:
std::istringstream file(
"chicken\n\n"
"dog\n\n"
"car\n\n\n"
"765\n");
Info i;
file >> i; // read one Info from the stream
std::cout << i; // print one Info
}
Demo

Read a line from file, using stream style

I have a simple text file, that has following content
word1 word2
I need to read it's first line in my C++ application.
Following code works, ...
std::string result;
std::ifstream f( "file.txt" );
f >> result;
... but result variable will be equal to "word1". It should be equal to "word1 word2" (first line of text file)
Yes, i know, that i can use readline(f, result) function, but is there a way to do the same, using >> style. This could be much more pretty.
Possible, some manipulators, i don't know about, will be useful here ?
Yes define a line class and define the operator >> for this class.
#include <string>
#include <fstream>
#include <iostream>
struct Line
{
std::string line;
// Add an operator to convert into a string.
// This allows you to use an object of type line anywhere that a std::string
// could be used (unless the constructor is marked explicit).
// This method basically converts the line into a string.
operator std::string() {return line;}
};
std::istream& operator>>(std::istream& str,Line& line)
{
return std::getline(str,line.line);
}
std::ostream& operator<<(std::ostream& str,Line const& line)
{
return str << line.line;
}
void printLine(std::string const& line)
{
std::cout << "Print Srting: " << line << "\n";
}
int main()
{
Line aLine;
std::ifstream f( "file.txt" );
f >> aLine;
std::cout << "Line: " << aLine << "\n";
printLine(aLine);
}
No, there isn't. Use getline(f, result) to read a line.
You can create a local that only has newlines as whitespace, but that would be a confusing hack. Here is an example doing just that with commas.