C++ - Modify a File Without Creating a New File - c++

I'm trying to open a file, and modify some contents inside. My first code looks like this,
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main(int argc, char** argv) {
int status = 0;
//error if no filename
if(argc == 1) {
cerr << "No file specified. Please specify a filename" << endl;
status = 1;
}
//open a file and modify
for (int i = 1; i < argc; ++i) {
string line = "";
string filename = argv[i];
ifstream infile;
infile.open(filename);
if(infile.fail()) {
status = 1;
cerr << filename << ": No Such File Exist" << endl;
}
else{
while(getline(infile, line)) {
auto index1 = line.find('(');
auto index2 = line.find(')');
cout << index1 << " " << index2 << endl;
auto itor = line.begin();
if(index1 != string::npos) line[index1] = '[';
if(index2 != string::npos) line[index2] = ']';
}
infile.close();
}
}
return status;
}
I know it's wrong to directly modify line because it won't change the content in the file. Is there a way that I can modify the file directly?(Without creating a new file, and output line to that)

You can:
Store the lines, modified and unmodified, in a std::vector<std::string>.
Close the file.
Open the file in write mode.
Save the lines, stored in the std::vector<std::string> to the file
Close the file.
It will be better to create separate functions for each step.
void readContents(std::string const& filename,
std::vector<std::string>& lines)
{
...
}
void updateContents(std::vector<std::string>& lines)
{
...
}
void WriteContents(std::string const& filename,
std::vector<std::string> const& lines)
{
...
}
and then call the from main.
int main(int argc, char* argv[])
{
...
string filename = argv[i];
std::vector<std::string> lines;
readContents(filename, lines);
updateContents(lines):
writeContents(filename, lines):
}

If the data you want to change doesn't change the size of the file in any way (i.e. you're not trying to write data that is longer or shorter than the existing data in the file) then yes it's possible, just overwrite the existing data once you find it, by changing the write position.
If, on the other hand, the data is of a different size, then it's very hard to do (you need to read everything and reposition it within the file), it's much easier to just write a new file and rename it.
Changing one kind of brace to another will not change the size of the file, so then you can easily do it. Just go through the file one character at a time, when you find a character you want to change, set the write pointer to the position of the character and write the new character.

Related

C++ Read in a file like a grid

I'm coding in C++ and I'm trying to read in a file that I'd like to access certain chars at later. As in, what is the char at (line x, char y), at any given point in the file.
My only thought right now is to look for a newline character, and somehow index them so that I can refer back to newline x, check the length of a line, and pull a char at whatever position given the line length.
I'm not sure if that is a good approach or not.
Try this (for character in line "lineNum" and column "columnNum"):
ifstream inf;
inf.open(filename); //filename being c-string
string str;
for (int i = 0; i < lineNum; i++)
{
std::getline(inf, str);
}
This way "str" stores the line you are interested in (automatically checks for newline character and stops).
Then you can use:
char chr = str[columnNum];
to store the character you want in "chr" variable. And don't forget:
inf.close();
Unfortunately, to my knowledge you need to repeat this process every time you want to access a character.
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#define FILENAME "File.txt"
class FileGrid {
public:
typedef std::vector<std::string> Line;
typedef std::vector<std::string>::const_iterator LineIter;
typedef std::vector<std::vector<std::string>> StringMap;
typedef std::vector<std::vector<std::string>>::const_iterator StringMapIter;
void FillGrid(char* fileName) {
grid.clear();
std::ifstream in(FILENAME, std::ifstream::in);
if (!in.is_open()) {
std::cout << "problem reading " << FILENAME << std::endl;
return;
}
std::string words;
std::string word;
std::stringbuf buffer;
while (in.is_open() && std::getline(in, words)) {
std::stringstream ss(words);
Line line;
while (ss >> word) {
line.push_back(word);
}
grid.push_back(line);
}
}
void PrintGrid() {
StringMapIter b = grid.begin();
StringMapIter e = grid.end();
std::cout << "\t\tFile Content:" << std::endl;
while(b != e) {
for (unsigned int i = 0; i < b->size(); ++i) {
std::cout << b->operator[](i) << " ";
}
std::cout << std::endl;
++b;
}
}
char const & GetChar(int lineNo, int charNo) {
// LineNo checks etc
Line const & line = grid[lineNo];
for(std::string const & word : line ) {
if(charNo > word.size() + 1) {
charNo -= word.size() + 1;
}
else {
return word[charNo];
}
}
throw std::exception("charNo higher");
}
private:
StringMap grid;
};
void main() {
FileGrid grid;
grid.FillGrid(FILENAME);
grid.PrintGrid();
std::cout << grid.GetChar(0, 3); // should return first line, 4th character
}
Not the best code I've ever written but pretty much what I could do in a short time.
FileGrid handles reading and accessing the data. It reads the file line by line and stores it in a std::vector. When it finishes reading a line, it pushes that into another std::vector. In the end, we have a (sort of) 2D array of strings.
Again, not the best code and definitely not the most optimized code but the idea is still the same: read from the file line by line, separate each word and put them into an array of strings. If you can't use STL, you can dynamically create a 2D array for each line but since I don't know the specific requirements of your question, I just wrote something simple and bruteforce to show you the main way of storing grid of strings into the memory.
As long as it works. But reading the entire file into memory, if that's an option, would be simpler.

c++ how to read a cfg file more complex?

I recently changed from c# to c++.
Ive been watching some tutorials on how to read a config file. I am probably asking it wrong, what I mean is:
I am making a program, but it's for multiple users. Every user will have his prefence on what will be in a textfile.
Example:
In my textfile ("items.txt"), i have default "ints" and configurable "ints".
10=5
90=2
50=9
In c#, if i remember correctly, i read all lines and if the line started with for example "10=", i splitted the text so my line would only be the configurable int left and i can use that easly in my program, it was something like:
string[] lines = File.ReadAllLines(path);
string str;
foreach (string line in lines)
{
if (line.StartsWith("10"))
{
str = line.Split('=')[1];
//I have what i need (str);
}
}
I did that for everything that i needed. So, what's the best way to do this in c++?
ALSO: I need to get a specific line for them, so i can use them all later on in my program.
Thanks a lot in advance!
You have 3 things in your files:
lines, which we can read with getline
keys (the part of the line before =)
values (the part after the =).
We can split the key and value by finding the = character.
So, you get something like:
std::ifstream file(path);
if (!file) {
std::cerr << "unable to open " << path << std::endl;
return false;
}
for (std::string line; getline(file, line); ) {
auto pos = line.find('=');
if (pos == line.end()) {
std::cerr << "skipping invalid line '" << line << "'\n";
continue;
}
auto key = line.substr(0, pos);
auto val = line.substr(pos);
// do something with key,val
}
If you want to convert the key and value strings above into int, you can use a std::stringstream to do the conversion, or use std::strtol.
This code is a C++ equivalent of the C# code you posted. This code by itself will compile, of course you have to fill something in for path instead of an empty string. If you need this somewhere in a method instead of as a program on itself, abandon the int main() and return 0 of course. Reply to this if something is unclear. I tried to set it up the same way your C# code is structured, so you would understand everything.
#include <fstream>
#include <vector>
#include <string>
int main()
{
std::string path = "";
std::ifstream file(path); //open the file from path, might want to check file.fail()
std::vector<std::string> lines;
std::string line;
while (file >> line)
lines.push_back(line);
std::string str;
for (const std::string& line : lines)
{
if (line.substr(0, 2).compare("10") == 0)
{
size_t foundIdx = line.find('=');
str = line.substr(foundIdx + 1);
}
}
file.close();
return 0;
}
Well in C++ we now use std::regex
For your case - I'd use the following:
#include <iostream>
#include <string>
#include <regex>
#include <iterator>
int main()
{
// Simple config file
std::string rules = "10=5\n 90=2\n50=9";
std::regex single_rule_regex("[0-9]+\\=[0-9]+");
std::regex single_rule_part_regex("[0-9]+");
auto rules_begin = std::sregex_iterator(rules.begin(),
rules.end(),
single_rule_regex);
auto rules_end = std::sregex_iterator();
for (auto it = rules_begin; it != rules_end; ++it)
{
std::smatch rule_match = *it;
std::string rule_str = rule_match.str();
std::cout << rule_str << std::endl;
// key=value
auto parts = std::sregex_iterator(rule_str.begin(),
rule_str.end(),
single_rule_part_regex);
std::string key = parts->str();
std::string value = (++parts)->str();
std::cout << key << " has " << value << std::endl;
}
}
Output for the gives rules is:
10=5
10 has 5
90=2
90 has 2
50=9
50 has 9

Fixing syntax of number of line reading function

I tried making a program earlier that tells the user then number of char, words, and lines in a text file. I made functions to determine the numbers of each, yet I was passing them by value. This resulted in an error since after reading the number of char it would be at the end of the file and then output zero for the other two. Now I cant seem to rewrite my functions so that the file is open and closed each time its checked for char, words, and lines. Any one see where my errors are?? Thanks! (just copied and pasted one of my functions for now).
int num_of_lines(ifstream file)
{
string myfile;
myfile = argv[1];
ifstream l;
l.open(myfile);
int cnt3 = 0;
string str;
while(getline(file, str))cnt3++;
l.close();
return(cnt3);
}
int main(int argc, char **argv)
{
int num_of_char(ifstream file);
string file;
file = argv[1];
if(argc == 1)die("usage: mywc your_file");
ifstream ifs;
ifs.open(file);
if(ifs.is_open())
{
int a, b, c;
a = num_of_lines(ifs);
cout <<"Lines: " << a << endl;
}
else
{
cerr <<"Could not open: " << file << endl;
exit(1);
}
ifs.close();
return(0);
}
There is no way to "reopen" a file other than knowing the name and creating a new ifstream, but you can use the seekg member function to set your read position in the file, and setting it to 0 will have the next read operation start from the beginning of the file.
A stream is not possible to copy, so you can't pass it "by value", but must pass it by reference.
int num_of_lines(ifstream &file)
{
int count = 0;
string str;
while (getline(file, str)) {
count++;
}
file.seekg(0);
return count;
}
For the full problem, I agree with Mats Petersson, though. Counting both characters, lines and words in one pass will be much more efficient than reading through the file three times.

Writing a C++ program that can merge files from the command line in Linux

I have written a C++ program that is supposed to open two text files (prog2a.dat and prog2b.dat) and write the contents of a specified range of lines to an output file (outfile.dat). I wrote a program that based on the example we were given (to take lines 5-15 from the first file and lines 4-12 of the second file and merge them into the output file) works perfectly. However, after asking my professor for some clarification on another part of the assignment, I found out I have not done this correctly. I have the code written so that it will always output the range of lines I mentioned earlier, but the program is actually supposed to allow the user to merge the files from the command line using whatever range they want by typing in the following command:
prog2 in1 5-15 in2 4-12 outfile
But I'm not sure how to tweak my current program to allow this to be done.
Here is the code that I have written, keep in mind that this works properly for the way it is written, but not how it is supposed to work for the purposes of the command line (hopefully that makes sense):
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
int main() {
// Create output file
std::ofstream outFile("outfile.dat", ios::out);
// Open input file 1, if can't be opened, exit
ifstream in1;
in1.open("prog2a.dat");
std::string line;
int count = 1;
if (!in1) {
cerr << "Open Failure" << endl;
exit(1);
} // end if
else {
while (std::getline(in1, line)) {
if (count >= 5 && count <= 15) {
outFile << line << "\n"; /*writes the contents of
lines 5-15 to outfile.dat*/
}
++count;
} // end while
} // end else
in1.close(); // close in1 (prog2a.dat)
outFile << "\n"; // add a blank line after the output from prog2a.dat
count = 1; // reset the line count to 1 before opening next file.
// Open input file 2, if can't be opened, exit
ifstream in2;
in2.open("prog2b.dat");
if (!in2) {
cerr << "Open Failure" << endl;
exit(1);
} // end if
else {
while (std::getline(in2, line)) {
if (count >= 4 && count <= 12) {
outFile << line << "\n"; /*writes the contents of the
lines 4-12 to outfile*/
}
++count;
} // end while
} // end else
in2.close(); // close in2 (prog2b.dat)
} // end main
Is there any simple way to make this work as I described using the command line? Also, I am supposed to break this up into three files, a header file, the program file, and a test file (the test file contains main() and should close the 3 open files and display any error messages), but I'm getting really confused as to what should go in the header file. I know the header file should contain class definitions and constructors, but don't really know how to make that work for this specific program? I'm extremely new to this so any pointers would be greatly appreciated.
The issue is that line numbers and file names are hardcoded in your main function. As mentioned in the comments, you need to handle main function arguments. Also your code contains duplication that can easily be moved to separate function (reading input files and copying required strings to output). I've removed some duplication by moving the related code to separate functions. You still need to check for errors: take a look at //TODO comments in the code:
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
bool lineNumbersFromString(const std::string& aString, int& startPos, int& endPos)
{
std::size_t pos = aString.find('-');
if (pos < 0 || pos >= aString.length())
{
return false;
}
std::string start = aString.substr(0, pos);
std::string end = aString.substr(pos + 1, aString.length()-1);
if (start.length() == 0 || end.length() == 0)
{
return false;
}
startPos = atoi(start.c_str());
endPos = atoi(end.c_str());
return true;
}
bool copyLinesToOutFile(std::string& inputFileName, int startLine, int endLine, std::ofstream& outFileStream)
{
ifstream inputFileStream;
inputFileStream.open(inputFileName.c_str());
if (!inputFileStream)
{
cerr << "Cannot open file: " << inputFileName << endl;
return false;
}
int lineCount = 0;
std::string line;
while (std::getline(inputFileStream, line))
{
if (lineCount >= startLine && lineCount <= endLine)
{
outFileStream << line << "\n";
}
++lineCount;
}
inputFileStream.close();
}
int main(int argc, char** argv)
{
if (argc != 6)
{
//Invalid number of arguments
//TODO: report error
return -1;
}
std::string firstFileName = argv[1];
std::string firstFileRange = argv[2];
std::string secondFileName = argv[3];
std::string secondFileRange = argv[4];
std::string outFileName = argv[5];
int firstStartPos = 0;
int firstEndPos = 0;
bool ok = false;
ok = lineNumbersFromString(firstFileRange, firstStartPos, firstEndPos);
//TODO: check error
// Create output file
std::ofstream outFile(outFileName.c_str(), ios::out);
ok = copyLinesToOutFile(firstFileName, firstStartPos, firstEndPos, outFile);
//TODO: check error
int secondStartPos = 0;
int secondEndPos = 0;
ok = lineNumbersFromString(secondFileRange, secondStartPos, secondEndPos);
//TODO: check error
ok = copyLinesToOutFile(secondFileName, secondStartPos, secondEndPos, outFile);
//TODO: check error
outFile.close();
return 0;
}
P.S. Hope this helps. Splitting it to separate files should not be much of an issue.

Why does my countlines function always return 0?

So I am making a program for a simple calendar app which reads in inputs from a file input.csv (its a text file with two columns which are seperated using commas and new lines for each command).
The first thing i want to do is count the number of lines from the input file, which is passed as the third argument in the command line, so I can make an array to hold each line separately but the function countLines always returns 0!
Project code:
#include<iostream>
#include<string>
#include<fstream>
using namespace std;
//Prototypes
int countLines (ifstream& countfiles);
int countLines(ifstream& countfile)
//counts number of lines in file passed to function
{
string line;
int numberOfLines;
numberOfLines = 0;
//reads through each line until end of file
while(getline(countfile, line))
{
numberOfLines++;
}
return numberOfLines;
}
int main (int argc, char* argv[])
{
if(argc != 3) cout << "Usage: calendar.out datafile inputfile";
//Create input streams to both files
ifstream apptsfp;
ifstream inputfp;
//Open streams to both files
apptsfp.open(argv[2]);
inputfp.open(argv[3]);
int numberOfInputs=0;
numberOfInputs = countLines(inputfp)-1;
cout << "number of input commands: " << numberOfInputs << endl;
return 0;
}
Almost certainly because you are failing to open your input file.
inputfp.open(argv[3]);
if (!inputfp.is_open())
{
cerr << "failed to open input file " << argv[3] << '\n';
return 1;
}
Files can fail to open for all sorts of reasons, you should always check for this.
BTW don't use an array to hold the input lines, use std::vector<std::string>. Then you can use push_back to add the lines to the vector. This will be easier and more efficient because you won't have to read the file twice. What more could you ask for!
std::vector<std::string> lines;
std::string line;
while (getline(inputfp, line))
lines.push_back(line);
It seems you only want two arguments, not three as you say in the question (the "first" argument is the program name). This means that the input file is in argc[2] instead, and argv[3] is a NULL pointer.
This means that your open call will fail, but you do not check for that.
Your access to argv[3] is incorrect. The second file name (third arg, including the program name in arg[0]) is in slot 2 (the array is zero-based).
Try:
apptsfp.open(argv[1]);
inputfp.open(argv[2])
You are trying to access argv[3] which is null. Try this :-
int main (int argc, char* argv[])
{
if(argc != 3)
cout << "Usage: calendar.out datafile inputfile";
//Create input streams to both files
ifstream apptsfp;
ifstream inputfp;
//Open streams to both files
apptsfp.open(argv[1]);
inputfp.open(argv[2]);
int numberOfInputs=0;
numberOfInputs = countLines(inputfp)-1;
cout << "number of input commands: " << numberOfInputs << endl;
return 0;
}