Removing a Line from a File without making a new file - c++

I'm trying to remove a single line from a file without creating a new file. For example in the file before it is modified it would be:
This
is
a
file
and after it would be:
This
a
file
However, with the way I'm currently trying to do it what happens is
This
a
file
I know I could do it by writing only the contents that I want into another file and then renaming that file and deleting the old one but I wanted to know if there is another way besides that.
I've tried using
if (string::npos != line.find(SPSID))
{
iPos = (pos - line.size() - 2);
stream.seekg(iPos);
for (int i = (pos - line.size() - 2); i < pos; i++)
{
//Sets input position to the beginning of the current line and replaces it with NULL
stream.put(0);
}
stream.seekp(iPos);
pos = stream.tellp();
}
as well as replacing stream.put(0); with stream.write(nullLine, iPos);
but neither have worked.
int Delete(string fileName, string SPSID)
{
//Variables
string line;
char input[MAX_CHAR];
fstream stream;
streamoff pos = 0;
streamoff iPos = 0;
//Opening and confirming opened
stream.open(fileName);
if (!stream.is_open())
{
cout << "File Did not open.\n" << endl;
return -1;
}
//Loops until the end of the file
do
{
//Gets one line from the file and converts it to c++ string
stream.getline(input, MAX_CHAR, '\n');
line.assign(input);
//Finds the current output position (which is the start of the next line)
pos = stream.tellp();
//Finds and checks if the SPSID is in the string. If it is then print to screen otherwise do nothing
if (string::npos != line.find(SPSID))
{
iPos = (pos - line.size() - 2);
stream.seekg(iPos);
for (int i = (pos - line.size() - 2); i < pos; i++)
{
//Sets input position to the begining of the current line and replaces it with ""
stream.put(0);
}
stream.seekp(iPos);
pos = stream.tellp();
}
} while (stream.eof() == false); //Checks that the end of the file has not been reached
stream << "Test" << endl;
//Resets the input and output positions to the begining of the stream
stream.seekg(0, stream.beg);
stream.seekp(0, stream.beg);
//Closing and Confirming closed
stream.close();
if (stream.is_open())
{
cout << "File did not close.\n" << endl;
return -2;
}
return 0;
}
I'm probably gonna have to make a new file and rename it but figured it was still worth asking if this is possible. :/

Related

Output issue with .CSV file

Whenever I attempt to output a line, it outputs the data from the file vertically instead of outputting the full line horizontally. My main goal is to output each line individually and remove commas and repeat till no more lines are in the CSV file.
An example when I run the code:
cout << data[1] << "\t";
Output:
Huggenkizz Pinzz White Dwarf Dildock Operknockity DeVille
What I'm trying to get is:
Huggenkizz Amanda 3/18/1997 Sales Associate 2 A A F
My CSV File:
ID,Last Name,First Name,DOB,DtHire,Title,Level,Region,Status,Gender
1,Huggenkizz,Amanda,3/18/1997,,Sales Associate,2,A,A,F
2,Pinzz,Bobby,5/12/1986,,Sales Associate,3,B,A,F
3,White,Snow,12/23/1995,,Sales Associate,2,C,A,F
4,Dwarf,Grumpy,9/8/1977,,Sales Associate,2,C,A,M
5,Dildock,Dopey,4/1/1992,,Sales Associate,1,B,A,M
6,Operknockity,Michael,10/2/1989,,Sales Associate,1,A,S,M
9,DeVille,Cruella,8/23/1960,,Sales Manager,,,A,F
My Code:
vector<string> SplitString(string s, string delimiter)
{
string section;
size_t pos = 0;
vector<string> annualSalesReport;
while ((pos = s.find(delimiter)) != string::npos) //finds string till, if not returns String::npos
{
section = (s.substr(0, pos)); // returns the substring section
annualSalesReport.push_back(section); // places comma split section into the next array
s.erase(0, pos + delimiter.length()); // removes the previous string up to the current pos
}
annualSalesReport.push_back((s));
return annualSalesReport;
}
int main()
{
vector<string> data;
string readLine;
ifstream myIFS;
myIFS.open("SalesAssociateAnnualReport.csv");
int lineCounter = 0;
while (getline(myIFS, readLine))
{
lineCounter++;
if (lineCounter > 1)
{
data = SplitString(readLine, ",");
if (data.size() > 1) //removes top line
{
cout << data[1]<< "\t";
}
}
}
myIFS.close();
return 0;
}
Please change your main function as follows
int main()
{
vector<vector<string>> data;
string readLine;
ifstream myIFS;
myIFS.open("SalesAssociateAnnualReport.csv");
int lineCounter = 0;
while (getline(myIFS, readLine))
{
lineCounter++;
if (lineCounter > 1)
{
vector<string> dataLine = SplitString(readLine, ",");
data.push_back(dataLine);
}
}
myIFS.close();
// output the first data line of csv file without delimiter and without first column
for (size_t i = 1; i < data[0].size(); i++)
{
cout << data[0][i] << '\t';
}
return 0;
}
to get your desired output of
Huggenkizz Amanda 3/18/1997 Sales Associate 2 A AF
without having to change your SplitString function.
Please be aware that C++ first array index is always 0 instead of 1.
I've separated the CSV file input processing and the output generation, just to follow the simple programming model IPO:
Input -> Process -> Output
Therefore I've introduced the matrix of strings vector<vector<string>> to store the whole desired CSV file data.
As mentioned in the comments, the SplitString function may be refactored and it should also be fixed to split the last two columns properly.
Hope it helps?

Arduino - SD text file remove row

I build script to delete one row from SD from .txt file.
Script works well but if I use longer string TextToRemove (for example length is 9) script ignore the line and println empty line. Additionally following condition is always true.
if (buffer.substring(0, buffer.length() - 1) != TextToRemove)
Basically idea is to
Create a new file *_tmp
Read first line from original file
Compare if line is equal to TestToRemove
If text is not equal println to tmp file, if is equal igonore the line
Repeat steps 2-4.
Delete original file
Rename tmp file (remove _tmp)
boolean RemovePin(String TextToRemove, String FileName) {
String buffer;
String FileName_new = FileName + "_tmp";
File myFile;
File myFile_new;
boolean Removed = false;
char filename2[FileName.length() + 1];
FileName.toCharArray(filename2, sizeof(filename2));
myFile = SD.open(FileName);
myFile_new = SD.open(FileName + "_tmp", FILE_WRITE);
if (myFile_new) {
if (myFile) {
while (myFile.available()) {
buffer = myFile.readStringUntil('\n');
if (buffer.substring(0, buffer.length() - 1) != TextToRemove) {
myFile_new.println(buffer.substring(0, buffer.length() - 1));
} else {
Removed = true;
}
}
myFile.close();
SD.remove(filename2);
} else {
Serial.print("error opening ");
Serial.println(FileName);
}
myFile_new.rename(SD.vwd(), filename2);
myFile_new.close();
} else {
Serial.println("error opening tmp file");
}
return Removed;
}
Thank you very much for your help

Getline Randomly Stops Working

I am trying to write a very basic program to read in a CSV and display it. The function, as you can see below, is a very basic one. It uses getline to read in each row (until the newline chracter) before displaying each cell. It was working fine and I was finetuning the loop to display each cell when getline simply stopped working. Without changing any of the code to do with getline, I compiled it and it would not read from the file. row is always empty. However, the ifstream is valid because the "test" string (which I inserted in response) reads in from sheet fine. Can anyone help me?
PS: I have had similar problems with getline before. Is it something to do with my system? It seems to work on and off
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>
using namespace std;
int main(int argc, char *argv[])
{
ifstream sheet( "StockTake.csv", ios::app );
if ( ! sheet.is_open() )
{
cout << "Cannot Open File!\n";
return 0;
}
else
{
cout << "Opened File\n";
}
//Now print each row (find the newline chracter at the end of each line)
/*string test;
sheet >> test;
cout << test;*/
for ( string row; ! sheet.eof(); getline(sheet, row, '\n') )
{
if (row == "")
{
cout << "Bad Read. Exiting\n";
return 0;
}
//Print out each row with a tab space between each cell
string cell;
//find the beginning and the end of each cell
for (int i = 0, j = 0; ; )
{
/*Checks if the cell is enclosed in quotes
The first time, j == i hence the -2 and +2
the +2 is required to "skip" out the apostrophes if
they are found*/
if (row.at(i) == '"')
{
i++;
j = i-2;
do
{
j = row.find('"', j+2);
} while (row.at(j+1) == '"');
/*Check for the "" apostrophe sign*/
}
else
{
j = row.find(',', i);
}
/*Print the Cell*/
if (j == string::npos) //if this is the last cell
{
cell = row.substr(i, row.size() - i);
}
else if ( j-i != 0)
{
cell = row.substr(i, j-i);
}
else
{
cell = "";
}
/*Check for the "" apostrophe sign, and replace
with " for each instance*/
if ( cell.find("\"\"", 0) != string::npos )
{
int pos = -2; //must start at zero
do
{
//Skips out the "current" apostrophe
//if there are more than one
pos = cell.find("\"\"", pos+2);
cell = cell.substr(0, pos) +
cell.substr(pos+1, cell.size()- (pos+1) );
} while (cell.find("\"\"", pos+2) != string::npos);
}
/*Display the Cell, only the space will be displayed
if the cell is empty*/
cout << cell << " ";
/*Find the next cell*/
if ( row.at(j) == '"' )
{
i = j+2;
}
else
{
i = j+1;
}
}
cout << "\n"; //Print newline character at the end of each line
}
return 0;
}

Alternating between reading and writing repeatedly

My objective is to read a file line by line, check if that line contains some number, and if so rewrite that line. Then continue reading the file.
I've successfully been able to do this for one line, but I can't figure out how to continue reading the rest of the file.
Here's how I replace one line (every line is a known fixed size):
while(getline(fs, line)){
if(condition){
pos = fs.tellg(); //gets current read position (end of the line I want to change)
pos -= line.length()+1; //position of the beginning of the line
fs.clear(); //switch to write mode
fs.seekp(pos); //seek to beginning of line
fs << new_data; //overwrite old data with new data (also fixed size)
fs.close(); //Done.
continue;
}
}
How do I switch back to read and continue the getline loop?
I had the same problem, TB-scale files and I wanted to modify some header information in the beginning of the file.
Obviously one has to leave enough room when one initially creates the file for any new content, because there is no way to increase the file size (besides appending to it) and the new line has to have the exact same line length as the original one.
Here is a simplification of my code:
#include <iostream>
#include <fstream>
using namespace std;
bool CreateDummy()
{
ofstream out;
out.open("Dummy.txt");
// skip: test if open
out<<"Some Header"<<endl;
out<<"REPLACE1 12345678901234567890"<<endl;
out<<"REPLACE2 12345678901234567890"<<endl;
out<<"Now ~1 TB of data follows..."<<endl;
out.close();
return true;
}
int main()
{
CreateDummy(); // skip: test if successful
fstream inout;
inout.open("Dummy.txt", ios::in | ios::out);
// skip test if open
bool FoundFirst = false;
string FirstText = "REPLACE1";
string FirstReplacement = "Replaced first!!!";
bool FoundSecond = false;
string SecondText = "REPLACE2";
string SecondReplacement = "Replaced second!!!";
string Line;
size_t LastPos = inout.tellg();
while (getline(inout, Line)) {
if (FoundFirst == false && Line.compare(0, FirstText.size(), FirstText) == 0) {
// skip: check if Line.size() >= FirstReplacement.size()
while (FirstReplacement.size() < Line.size()) FirstReplacement += " ";
FirstReplacement += '\n';
inout.seekp(LastPos);
inout.write(FirstReplacement.c_str(), FirstReplacement.size());
FoundFirst = true;
} else if (FoundSecond == false && Line.compare(0, SecondText.size(), SecondText) == 0) {
// skip: check if Line.size() >= SecondReplacement.size()
while (SecondReplacement.size() < Line.size()) SecondReplacement += " ";
SecondReplacement += '\n';
inout.seekp(LastPos);
inout.write(SecondReplacement.c_str(), SecondReplacement.size());
FoundSecond = true;
}
if (FoundFirst == true && FoundSecond == true) break;
LastPos = inout.tellg();
}
inout.close();
return 0;
}
The input is
Some Header
REPLACE1 12345678901234567890
REPLACE2 12345678901234567890
Now ~1 TB of data follows...
The output is:
Some Header
Replaced first!!!
Replaced second!!!
Now ~1 TB of data follows...

How to get the last but not empty line in a txt file

I want to get the last but not empty line in a txt file.
This is my code:
string line1, line2;
ifstream myfile(argv[1]);
if(myfile.is_open())
{
while( !myfile.eof() )
{
getline(myfile, line1);
if( line1 != "" || line1 != "\t" || line1 != "\n" || !line1.empty() )
line2 = line1;
}
myfile.close();
}
else
cout << "Unable to open file";
The problem is I cannot check the empty line.
Okay, let's start with the obvious part. This: while( !myfile.eof() ) is essentially always wrong, so you're not going to detect the end of the file correctly. Since you're using getline to read the data, you want to check its return value:
while (getline(myfile, line1)) // ...
Likewise, the logic here:
if( line1 != "" || line1 != "\t" || line1 != "\n" || !line1.empty() )
line2 = line1;
...is clearly wrong. I'm guessing you really want && instead of || for this. As it stands, the result is always true, because no matter what value line1 contains, it must be unequal to at least one of those values (i.e., it can't simultaneously contain only a tab and contain only a new-line and contain nothing at all -- but that would be necessary for the result to be false). Testing for both !line1.empty() and line1 != "" appears redundant as well.
Why not read the file backwards? That way you don't have to scan the entire file to accomplish this. Seems like it ought to be possible.
int main(int argc, char **argv)
{
std::cout<<"Opening "<<fn<<std::endl;
std::fstream fin(fn.c_str(), std::ios_base::in);
//go to end
fin.seekg(0, std::ios_base::end);
int currpos = fin.tellg();
//go to 1 before end of file
if(currpos > 0)
{
//collect the chars here...
std::vector<char> chars;
fin.seekg(currpos - 1);
currpos = fin.tellg();
while(currpos > 0)
{
char c = fin.get();
if(!fin.good())
{
break;
}
chars.push_back(c);
currpos -= 1;
fin.seekg(currpos);
}
//do whatever u want with chars...
//this is the reversed order
for(std::vector<char>::size_type i = 0; i < chars.size(); ++i)
{
std::cout<<chars[i];
}
//this is the forward order...
for(std::vector<char>::size_type i = chars.size(); i != 0; --i)
{
std::cout<<chars[i-1];
}
}
return 0;
}
It wouldn't be enough to change your ||'s to &&'s to check if the line is empty. What if there are seven spaces, a tab character, another 3 spaces and finally a newline? You can't list all the ways of getting only whitespace in a line. Instead, check every character in the line to see if it is whitespace.
In this code, is_empty will be false if any non-space character is found in the line.
bool is_empty = true;
for (int i = 0; i < line.size(); i++) {
char ch = line[i];
is_empty = is_empty && isspace(ch);
}
Full solution:
#include <iostream>
#include <fstream>
#include <cctype>
#include <string>
using namespace std;
int main(int argc, char* argv[]) {
string line;
string last_line;
ifstream myfile(argv[1]);
if(myfile.is_open())
{
while( getline(myfile, line) ) {
bool is_empty = true;
for (int i = 0; i < line.size(); i++) {
char ch = line[i];
is_empty = is_empty && isspace(ch);
}
if (!is_empty) {
last_line = line;
}
}
myfile.close();
cout << "Last line: " << last_line << endl;
}
else {
cout << "Unable to open file";
}
return 0;
}
Additional to what the others said:
You can avoid reading whitespace by doing myfile >> std::ws before you call std::getline(). This will consume all leading whitespaces.
Then your condition reduces to !line1.empty(). This would also work when the line contains nothing but several whitespaces, for which your version fails.
I wasn't able to google an appropriate get_last_line function for my needs and here's what i came up with. You can even read multiple non-empty last lines by recalling the instream get_last_line func without resetting the seeker. It supports a 1 char only file. I added the reset parameter, which can be set to ios_base::end to allow output operations after reading the last line(s)
std::string& get_last_line(
std::istream& in_stream,
std::string& output = std::string(),
std::ios_base::seekdir reset = std::ios_base::cur)
{
output.clear();
std::streambuf& buf = *in_stream.rdbuf();
bool text_found = false;
while(buf.pubseekoff(-1, std::ios_base::cur) >= 0)
{
char c = buf.sgetc();
if(!isspace(c))
text_found = true;
if(text_found)
{
if(c == '\n' || c == -1)
break;
output.insert(0, sizeof c, c);
}
}
buf.pubseekoff(0, reset);
return output;
}
std::string& get_last_line(
const std::string& file_name,
std::string& output = std::string())
{
std::ifstream file_in(
file_name.c_str(),
std::ios_base::in | std::ios_base::ate);
if(!file_in.is_open())
{
output.clear();
return output;
}
get_last_line(file_in, output);
file_in.close();
return output;
}