ifstream: /dev/stdin is not working the same as std::cin - c++

For my formation, an exercise ask us to create a program similar to the linux 'cat' command.
So to read the file, i use an ifstream, and everything work fine for regular file.
But not when i try to open /dev/ files like /dev/stdin: the 'enter' is not detected and so, getline really exit only when the fd is being closed (with a CTRL-D).
The problem seems to be around how ifstream or getline handle reading, because with the regular 'read' function from libc, this problem is not to be seen.
Here is my code:
#include <iostream>
#include <string>
#include <fstream>
#include <errno.h>
#ifndef PROGRAM_NAME
# define PROGRAM_NAME "cato9tails"
#endif
int g_exitCode = 0;
void
displayErrno(std::string &file)
{
if (errno)
{
g_exitCode = 1;
std::cerr << PROGRAM_NAME << ": " << file << ": " << strerror(errno) << std::endl;
}
}
void
handleStream(std::string file, std::istream &stream)
{
std::string read;
stream.peek(); /* try to read: will set fail bit if it is a folder. */
if (!stream.good())
displayErrno(file);
while (stream.good())
{
std::getline(stream, read);
std::cout << read;
if (stream.eof())
break;
std::cout << std::endl;
}
}
int
main(int argc, char **argv)
{
if (argc == 1)
handleStream("", std::cin);
else
{
for (int index = 1; index < argc; index++)
{
errno = 0;
std::string file = std::string(argv[index]);
std::ifstream stream(file, std::ifstream::in);
if (stream.is_open())
{
handleStream(file, stream);
stream.close();
}
else
displayErrno(file);
}
}
return (g_exitCode);
}
We can only use method from libcpp.
I have search this problem for a long time, and i only find this post where they seems to have a very similar problem to me:
https://github.com/bigartm/bigartm/pull/258#issuecomment-128131871
But found no really usable solution from them.
I tried to do a very ugly solution but... well...:
bool
isUnixStdFile(std::string file)
{
return (file == "/dev/stdin" || file == "/dev/stdout" || file == "/dev/stderr"
|| file == "/dev/fd/0" || file == "/dev/fd/1" || file == "/dev/fd/2");
}
...
if (isUnixStdFile(file))
handleStream(file, std::cin);
else
{
std::ifstream stream(file, std::ifstream::in);
...
As you can see, a lot of files are missing, this can only be called a temporary solution.
Any help would be appreciated!

The following code worked for me to deal with /dev/fd files or when using shell substitute syntax:
std::ifstream stream(file_name);
std::cout << "Opening file '" << file_name << "'" << std::endl;
if (stream.fail() || !stream.good())
{
std::cout << "Error: Failed to open file '" << file_name << "'" << std::endl;
return false;
}
while (!stream.eof() && stream.good() && stream.peek() != EOF)
{
std::getline(stream, buffer);
std::cout << buffer << std::endl;
}
stream.close();
Basically std::getline() fails when content from the special file is not ready yet.

Related

Trying to make something write to a file in c++

I tried programming a file writer, but when i try to write to a file with something that has multiple words it will suddenly create files.
My code
#include <fstream>
#include <iostream>
#include <unistd.h>
int main(int argc, char *argv[]) {
char cwd[256];
while (true) {
getcwd(cwd, 256);
std::string cwd_s = (std::string)cwd;
std::string Input;
std::cout << cwd_s << "> ";
std::cin >> Input;
std::ofstream file(Input);
std::cout << "cmd /";
std::cin >> Input;
file << Input;
};
for (int i; i < argc; i++) {
std::cout << argv[i] << '\n';
};
return 0;
}
I expected to get this:
C:\Users\code> File.txt
cmd /hello world!
File.txt
hello world!
But it only had "hello", it created another file named world!
I have tried changing the code, but to no avail.
So I have wrote this code that I think does what you expect. The behavior you were seing is because you used the same string to store the filename and the user input. Also you redefined a new file every loop (without closing the previous one). I added a signal handler since if you press Ctrl+C the program would quit without saving/closing the file.
I added comments about how you can make a better CLI interface (if you're interested)
#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
std::ofstream outfile;
void signalHandler(int signum) {
outfile.close();
exit(signum);
}
int main() {
char cwd[256];
if (getcwd(cwd, sizeof(cwd)) != NULL) {
std::cout << cwd << "> ";
} else {
std::cerr << "Error: Could not get current working directory." << std::endl;
return 1;
}
std::string filename;
std::getline(std::cin, filename);
outfile.open(filename);
// We intercept the Ctrl+C signal to close the file before exiting. Else nothing will be written to it.
// You can also use Ctrl+D (EOF: End Of File) to exit the program.
// The best praticte would be to implement a command line interface with a "quit" command. (like a map<string, function> for example)
signal(SIGINT, signalHandler);
// Another good practice is to check if the file did open correctly.
if (!outfile.is_open()) {
std::cerr << "Error: Could not open file for writing." << std::endl;
return 1;
}
std::cout << "cmd / ";
char ch;
while (std::cin.get(ch)) {
outfile.put(ch);
if (ch == '\n') {
std::cout << "cmd / ";
}
}
outfile.close();
return 0;
}
Hope it will help you ! And if you have any question about the code feel free to ask I'll explain !

Segmentation fault: unable to fix the problem

I'm new to C++, and I'm trying to write a project that interacts through command line. Right now, whenever I run my main (which is the executable), I always receive a segmentation fault error when the main program finished.
Edit comment:
I'm told by tutor to use as little as C++ features such as vectors or strings ... I'm also very new to C++, so i'm trying to utilize as many basic C functions as I can.
I'm
My main function looks like this:
int main(int argc, char** argv) {
cout << "starting mvote..." << endl;
int run_flag = 1;
char* actionBuffer = (char*)malloc(100 * sizeof(char));
char* action = (char*)malloc(16 * sizeof(char));
char* readPtr;
char exit[4] = { 'e','x','i','t' };
//parse command line argumentand get the filename
char* filename = argv[2];
cout << filename;
FILE* fp;
char line[64];
//from here, I'm opening the file and read it by lines
fp = fopen(filename, "r");
if (fp == NULL) {
cout << "file not exists";
return -1;
}
while (fgets(line, 64, fp) != NULL) {
cout << line << "\n";
}
fclose(fp);
while (run_flag == 1) {
cout << "what do you want?\n " << endl;
cin.getline(actionBuffer, 1024);
if (strcmp(actionBuffer, exit) == 0) {
cout << "bye!";
run_flag = 0;
break;
}
//if not exit, Look for the space in the input
readPtr = strchr(actionBuffer, ' ');
int size = readPtr - actionBuffer;
//extract the operation
strncpy(action, actionBuffer, size);
for (int i = 0; i < size; i++) {
cout << "operation:" << action[i];
}
// depend on the operation specified before the first empty space
run_flag = 0;
}
free(actionBuffer);
free(action);
return 0;
}
Description:
I first try to open up a csv file which lies in the same folder as main, and I read the file line by line. Then, I just implement a simple command where you can type exit and quit the program.
I allocate two memory, actionBuffer and action, which are used to hold command
Problem: a segmentation fault [core dumped] always exists when I type exit and hit enter, and then the process finished.
Research: So I learned that segmentation fault is due to accessing a memory that does not belongs to me. But where in my program am I trying to access such a memory?
Any advice is appreciated! Thank you.
Just to give you an idea, this would be an example of C++ code
#include<iostream>
#include<fstream>
#include<string_view>
#include<string>
#include<sstream>
#include<exception>
int main(int argc, char** argv) {
std::cout << "starting mvote...\n";
//parse command line argumentand get the filename
std::string filename = argv[2]; // NO CHECKS!
std::cout << filename <<'\n';
//from here, I'm opening the file and read it by lines
{
std::ifstream ifs(filename);
if (!ifs) {
throw std::invalid_argument("file not exists");
}
std::string line;
while (std::getline(ifs, line)) {
std::cout << line << '\n';
}
}
bool run_flag = true;
while (run_flag) {
std::cout << "what do you want?\n";
std::string userInput;
std::getline(std::cin, userInput);
if (userInput == "exit") {
std::cout << "bye!\n";
return 0;
}
std::stringstream userInputSs(userInput);
std::string operation;
while(userInputSs >> operation){
std::cout << "operation: " << operation << '\n';
}
}
}

ofstream outputfile() in while loop usually makes empty file

I encountered a funny issue with this code but I am wondering why this happened:
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <wiringPi.h>
#define BUTTON_PORT 25
#define FILE_PATH "/path/to/output.txt"
int main(void) {
int buttonState = 0;
int pastButtonState = 0;
int buttonCounter = 0;
if (wiringPiSetupGpio() == -1) return 1;
pinMode(BUTTON_PORT, INPUT);
while(buttonCounter < 100){
if (buttonState == 1 && pastButtonState == 0) buttonCounter++;
usleep(100);
ofstream outputfile(FILE_PATH);
outputfile << buttonCounter << endl;
outputfile.flush();
outputfile.close();
pastButtonState = buttonState;
}
I expected output.txt to show current # of buttonCounter,
but file is always empty, and 1 of 10 times the file shows # of buttonCounter.
(with continuously exec less /path/to/output.txt)
I know certain repeat of ofstream outputfile in while loop is not a good solution, but I have no idea why output.txt is usually empty.
I might think it's ugly to use ofstream outputfile in while loop since ofstream outputfile takes some time since it's a bit high-level function.
While putting outputfile -related function into
if (buttonState == 1 && pastButtonState == 0) {...}
works fine. Any ideas?
I would a add check to make sure the file was opened successfully. Also, the results of executing less on the file is not a reliable method to test the contents of the file while you are running the program. usleep(100) does not give you enough time to check the contents of the file before it is opened again.
I suggest the following change to your code.
while(buttonCounter < 100){
if (buttonState == 1 && pastButtonState == 0) buttonCounter++;
writeToFile(buttonCounter, FILE_PATH);
showContentsOfFile(FILE_PATH);
`
pastButtonState = buttonState;
}
where writeToFile is:
void writeToFile(int buttonState, std::string const& file)
{
std::ofstream outputfile(file);
if ( outputfile )
{
outputfile << buttonCounter << std::endl;
}
else
{
std::cerr << "Unable to open " << file << " for writing to.\n";
}
}
and showContentsOfFile is:
void showContentsOfFile(std::string const& file)
{
std::ifstream inputfile(file);
if ( outputfile )
{
int buttonCounter;
if ( inputfile >> buttonCounter )
{
std::cout << "Button counter: " << buttonCounter << std::endl;
}
else
{
std::cerr << "Unable to read buttonCounter from file " << file << std::endl;
}
}
else
{
std::cerr << "Unable to open " << file << " for reading from.\n";
}
}

Creating a class with an open file stream

I've created a class which is supposed to read in DNA sequences: It contains an if stream private member:
Interface:
class Sequence_stream {
const char* FileName;
std::ifstream FileStream;
std::string FileFormat;
public:
Sequence_stream(const char* Filename, std::string Format);
NucleotideSequence get();
};
Implementation:
Sequence_stream::Sequence_stream(const char* Filename, std::string Format)
{
FileName = Filename;
FileStream.open(FileName);
FileFormat = Format;
std::cout << "Filestream is open: " << FileStream.is_open() << std::endl;
}
NucleotideSequence Sequence_stream::get()
{
if (FileStream.is_open())
{
char currentchar;
int basepos = 0;
std::string name;
std::vector<Nucleotide> sequence;
currentchar = FileStream.get();
if (currentchar == '>' && false == FileStream.eof()) { // Check that the start of the first line is the fasta head character.
currentchar = FileStream.get(); // Proceed to get the full name of the sequence. Get characters until the newline character.
while(currentchar != '\n' && false == FileStream.eof())
{
if (true == FileStream.eof()) {
std::cout << "The file ends before we have even finished reading in the name. Returning an empty NucleotideSequence" << std::endl;
return NucleotideSequence();
}
name.append(1, currentchar);
currentchar = FileStream.get();
} // done getting names, now let's get the sequence.
currentchar = FileStream.get();
while(currentchar != '>' && false == FileStream.eof())
{
if(currentchar != '\n'){
basepos++;
sequence.push_back(Nucleotide(currentchar, basepos));
}
currentchar = FileStream.get();
}
if(currentchar == '>')
{
FileStream.unget();
}
return NucleotideSequence(name, sequence);
} else {
std::cout << "The first line of the file was not a fasta format description line beginning with '>'. Are you sure the file is of FASTA format?" << std::endl;
return NucleotideSequence();
}
} else {
std::cout << "The filestream is not open..." << std::endl;
return NucleotideSequence();
}
}
However if I test it:
int main()
{
std::cout << "Let's try and read in a sequence!" << std::endl;
std::cout << "First we'll create a stream!" << std::endl;
Sequence_stream MyDNAStream("~/Dropbox/1_20dd5.fasta", "fasta");
std::cout << "Done!" << std::endl;
std::cout << "Now let's try and get a sequence!" << endl;
NucleotideSequence firstsequence = MyDNAStream.get();
return 0;
}
I see that the if stream is not open:
Let's try and read in a sequence!
First we'll create a stream!
Filestream is open: 0
Done!
The filestream is not open...
logout
[Process completed]
Although I thought the constructor function opens the if stream. What do I need to do to correct this so as the object is created and contains an open stream? (I know I'm yet to include a destructor which will close the stream upon destruction of the object).
Thanks,
Ben.
Your example shows that is_open returned false. I think you should check in your constructor that the file is indeed open, and throw if not.
In your case, I suspect this is due to passing "~/Dropbox/1_20dd5.fasta" as an input parameter. Did you test with a full pathname, with no ~? I have no knowledge of a C++ library that handles real path expansion (like python's os.path).

std::ifstream setting fail() even when no error

Using GCC 4.7.3 on Cygwin 1.7.24. Compiler options include: -std=gnu++11 -Wall -Wextra
I am working on a command line application and I needed to be able to load and save a set of strings so I wrote a quick wrapper class around std::set to add load and save methods.
// KeySet.h
#ifndef KEYSET_H
#define KEYSET_H
#include <cstdlib>
#include <sys/stat.h>
#include <cerrno>
#include <cstring>
#include <string>
#include <set>
#include <iostream>
#include <fstream>
inline bool file_exists (const std::string& filename)
{
/*
Utility routine to check existance of a file. Returns true or false,
prints an error and exits with status 2 on an error.
*/
struct stat buffer;
int error = stat(filename.c_str(), &buffer);
if (error == 0) return true;
if (errno == ENOENT) return false;
std::cerr << "Error while checking for '" << filename << "': " << strerror(errno) << std::endl;
exit (2);
}
class KeySet
{
private:
std::string filename;
std::set<std::string> keys;
public:
KeySet() {}
KeySet(const std::string Pfilename) : filename(Pfilename) {}
void set_filename (const std::string Pfilename) {filename = Pfilename;}
std::string get_filename () {return filename;}
auto size () -> decltype(keys.size()) {return keys.size();}
auto cbegin() -> decltype(keys.cbegin()) {return keys.cbegin();}
auto cend() -> decltype(keys.cend()) {return keys.cend();}
auto insert(const std::string key) -> decltype(keys.insert(key)) {return keys.insert(key);}
void load ();
void save ();
};
void KeySet::load ()
{
if (file_exists(filename)) {
errno = 0;
std::ifstream in (filename, std::ios_base::in);
if (in.fail()) {
std::cerr << "Error opening '" << filename << "' for reading: " << strerror(errno) << std::endl;
exit (2);
}
std::string token;
if (token.capacity() < 32) token.reserve(32);
while (in >> token) keys.insert(token);
if (!in.eof()) {
std::cerr << "Error reading '" << filename << "': " << strerror(errno) << std::endl;
exit (2);
}
in.clear(); // need to clear flags before calling close
in.close();
if (in.fail()) {
std::cerr << "Error closing '" << filename << "': " << strerror(errno) << std::endl;
exit (2);
}
}
}
void KeySet::save ()
{
errno = 0;
std::ofstream out (filename, std::ios_base::out);
if (out.fail()) {
std::cerr << "Error opening '" << filename << "' for writing: " << strerror(errno) << std::endl;
exit (2);
}
for (auto key = keys.cbegin(), end = keys.cend(); key != end; ++key) {
out << *key << std::endl;
}
out.close();
if (out.fail()) {
std::cerr << "Error writing '" << filename << "': " << strerror(errno) << std::endl;
exit (2);
}
}
#endif
//
Here's a quick program to test the load method.
// ks_test.cpp
#include "KeySet.h"
int main()
{
KeySet test;
std::string filename = "foo.keys.txt";
test.set_filename(filename);
test.load();
for (auto key = test.cbegin(), end = test.cend(); key != end; ++key) {
std::cout << *key << std::endl;
}
}
The data file just has "one two three" in it.
When I go to run the test program, I get the following error from my test program:
$ ./ks_test
Error closing 'foo.keys.txt': No error
Both cppreference.com and cplusplus.com say that the close method should set the fail bit on error. The save method works fine, and the load method works correctly if I comment out the error check after the close. Should this really work or have I misunderstood how close is supposed to work? Thanks in advance.
Edited to clarify, fix typo's and adjust code per Joachim Pileborg's and Konrad Rudolph's comments.
Edited to add solution to the code.
You have two errors here: The first is about how you do your reading, more specifically the loop for reading. The eof flag will not be set until after you tried to read and the read failed. Instead you should do like this:
while (in >> token) { ... }
Otherwise you will loop one time to many and try to read beyond the end of the file.
The second problem is the one you notice, and it depends on the the first problem. Since you try to read beyond the end of the file, the stream will set failbit causing in.fail() to return true even though there is no real error.
As it turns out, the close method for ifstream (and I assume all other IO objects) DOES NOT clear the error flags before closing the file. This means you need to add an explicit clear() call before you close the stream after end of file if you are checking for errors during the close. In my case, I added in.clear(); just before the in.close(); call and it is working as I expect.