I would like to know if it is possible to inherit from std::ostream, and to override flush() in such a way that some information (say, the line number) is added to the beginning of each line. I would then like to attach it to a std::ofstream (or cout) through rdbuf() so that I get something like this:
ofstream fout("file.txt");
myostream os;
os.rdbuf(fout.rdbuf());
os << "this is the first line.\n";
os << "this is the second line.\n";
would put this into file.txt
1 this is the first line.
2 this is the second line.
flush() wouldn't be the function to override in this context, though you're on the right track. You should redefine overflow() on the underlying std::streambuf interface. For example:
class linebuf : public std::streambuf
{
public:
linebuf() : m_sbuf() { m_sbuf.open("file.txt", std::ios_base::out); }
int_type overflow(int_type c) override
{
char_type ch = traits_type::to_char_type(c);
if (c != traits_type::eof() && new_line)
{
std::ostream os(&m_sbuf);
os << line_number++ << " ";
}
new_line = (ch == '\n');
return m_sbuf.sputc(ch);
}
int sync() override { return m_sbuf.pubsync() ? 0 : -1; }
private:
std::filebuf m_sbuf;
bool new_line = true;
int line_number = 1;
};
Now you can do:
linebuf buf;
std::ostream os(&buf);
os << "this is the first line.\n"; // "1 this is the first line."
os << "this is the second line.\n"; // "2 this is the second line."
Live example
James Kanze's classic article on Filtering Streambufs has a very similar example which puts a timestamp at the beginning of every line. You could adapt that code.
Or, you could use the Boost tools that grew out of the ideas in that article.
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/array.hpp>
#include <cstring>
#include <limits>
// line_num_filter is a model of the Boost concept OutputFilter which
// inserts a sequential line number at the beginning of every line.
class line_num_filter
: public boost::iostreams::output_filter
{
public:
line_num_filter();
template<typename Sink>
bool put(Sink& snk, char c);
template<typename Device>
void close(Device&);
private:
bool m_start_of_line;
unsigned int m_line_num;
boost::array<char, std::numeric_limits<unsigned int>::digits10 + 4> m_buf;
const char* m_buf_pos;
const char* m_buf_end;
};
line_num_filter::line_num_filter() :
m_start_of_line(true),
m_line_num(1),
m_buf_pos(m_buf.data()),
m_buf_end(m_buf_pos)
{}
// put() must return true if c was written to dest, or false if not.
// After returning false, put() with the same c might be tried again later.
template<typename Sink>
bool line_num_filter::put(Sink& dest, char c)
{
// If at the start of a line, print the line number into a buffer.
if (m_start_of_line) {
m_buf_pos = m_buf.data();
m_buf_end = m_buf_pos +
std::snprintf(m_buf.data(), m_buf.size(), "%u ", m_line_num);
m_start_of_line = false;
}
// If there are buffer characters to be written, write them.
// This can be interrupted and resumed if the sink is not accepting
// input, which is why the buffer and pointers need to be members.
while (m_buf_pos != m_buf_end) {
if (!boost::iostreams::put(dest, *m_buf_pos))
return false;
++m_buf_pos;
}
// Copy the actual character of data.
if (!boost::iostreams::put(dest, c))
return false;
// If the character copied was a newline, get ready for the next line.
if (c == '\n') {
++m_line_num;
m_start_of_line = true;
}
return true;
}
// Reset the filter object.
template<typename Device>
void line_num_filter::close(Device&)
{
m_start_of_line = true;
m_line_num = 1;
m_buf_pos = m_buf_end = m_buf.data();
}
int main() {
using namespace boost::iostreams;
filtering_ostream myout;
myout.push(line_num_filter());
myout.push(std::cout);
myout << "this is the first line.\n";
myout << "this is the second line.\n";
}
Related
I have an assignment of saving some objects data in a specific order of its data members, I'll try to simplfy . Consider this Base class constructor from a binary file. (Please note that it was not my choice to use char *) .
Base(ifstream & in_file) {
int n;
in_file.read((char *)&n, sizeof(n));
m_var = new char[n + 1];
in_file.read(m_var, n);
m_var[n] = '\0';
in_file.read((char *)&m_intvar, sizeof(m_intvar));
}
It has to initialize m_var (char *) and another integer variable. This code works, though it requiers to save the length of the char * for me to allocate the memory.
The problem starts here. I was instructed not to save the size of the string, but to only enter a \n after each value i write to the file. So I need some how to read the file, and get the string until the \n character.
I was thinking about reading char by char, but couldn't find a way to do it, I assume there is an istream function that offers that. Some similar function to >> of a text file would also be good I assume.
After consulting cppreference.com I end up as follows:
#include <fstream>
#include <iostream>
#include <cstring>
class Base
{
public:
Base(std::istream & in_file) { // NOTE: changed to istream to allow reading from any stream not just files
in_file.read((char *)&n, sizeof(n));
char buffer[1024];
in_file.get(buffer, sizeof(buffer), '\n');
size_t gcount = in_file.gcount();
if (in_file.get() != '\n')
{
throw std::runtime_error("binary string to long"); // you may want to implement a loop here using peek() to check for newline
}
m_var = new char[gcount];
std::copy(buffer, buffer + gcount, m_var);
}
Base(int num, const char* strg)
: n(num)
, m_var(strdup(strg))
{
}
bool operator == (const Base& rhs)
{
return n == rhs.n && strcpy(m_var, rhs.m_var);
}
void write(std::ostream& out)
{
out.write((char*)&n, sizeof(n));
out.write(m_var, strlen(m_var));
out.write("\n", 1);
}
int n;
char* m_var = nullptr;
~Base()
{
delete m_var;
}
};
int main(int, char**)
{
Base b1(10, "Hello Word!");
{
std::ofstream out("testfile.bin");
b1.write(out);
}
std::ifstream in("testfile.bin");
Base b2(in);
if (b1 == b2)
{
std::cout << "read ok!" << std::endl;
}
else
{
std::cout << "read failed!" << std::endl;
}
return 0;
}
4 years later but I was having a similar problem and found this post.
You can read char by char as you mention, with a loop.
int i;
for(i=0;;i++){
cout<<i;
in_file.read(&buffer[i],sizeof(char));
if buffer[i]=='\n') break;
}
The only problem with the code I came up is it saves the '\n'. But you can and should replace it to the NULL char '\0' after having found the new line '\n'.
(buffer[i]=='\0')
Please correct me if I am mistaken.
I want to write/read data from a file. Is it possible to divide the file (inside the code) in multiple Strings/Sections? Or read data untill a specific line?
Just like: "Read the Data untill line 32, put it inside a String, read the next 32 lines and put it into another string"
Im already know how to read and find data with seekp but i dont really like it because my code always gets to long.
I already found some code but i dont understand it how it works:
dataset_t* DDS::readFile(std::string filename)
{
dataset_t* dataset = NULL;
std::stringstream ss;
std::ifstream fs;
uint8_t tmp_c;
try
{
fs.open(filename.c_str(), std::ifstream::in);
if (!fs)
{
std::cout << "File not found: " << filename << std::endl;
return NULL;
}
while(fs.good())
{
fs.read((char*)&tmp_c, 1);
if (fs.good()) ss.write((char*)&tmp_c, 1);
}
fs.close();
dataset = new dataset_t();
const uint32_t bufferSize = 32;
char* buffer = new char[bufferSize];
uint32_t count = 1;
while(ss.good())
{
ss.getline(buffer, bufferSize);
dataitem_t dataitem;
dataitem.identifier = buffer;
dataitem.count = count;
dataset->push_back(dataitem);
count++;
}
return dataset;
}
catch(std::exception e)
{
cdelete(dataset);
return NULL;
}
}
The Code edits a binary save file.
Or can someone link me a website where i can learn more about buffers and stringstreams?
You could create some classes to model your requirement: a take<N> for 'grab 32 lines', and a lines_from to iterate over lines.
Your lines_from class would take any std::istream: something encoded, something zipped, ... as long as it gives you a series of characters. The take<N> would convert that into array<string, N> chunks.
Here's a snippet that illustrates it:
int main(){
auto lines = lines_from{std::cin};
while(lines.good()){
auto chunk = take<3>(lines);
std::cout << chunk[0][0] << chunk[1][0] << chunk[2][0] << std::endl;
}
}
And here are the supporting classes and functions:
#include <iostream>
#include <array>
class lines_from {
public:
std::istream ∈
using value_type = std::string;
std::string operator*() {
std::string line;
std::getline(in, line);
return line;
}
bool good() const {
return in.good();
}
};
template<int N, class T>
auto take(T &range){
std::array<typename T::value_type, N> value;
for (auto &e: value) { e = *range; }
return value;
}
(demo on cpp.sh)
This is follow up to my question posted on codereview - Colorful output on terminal where I was trying to output coloured strings on terminal and detect it via isatty() call. However as #Jerry Coffin pointed out -
You use isatty to check whether standard output is connected to a terminal, regardless of what stream you're writing to. This means the rest of the functions only work correctly if you pass std::cout as the stream to which they're going to write. Otherwise, you may allow formatting when writing to something that's not a TTY, and you may prohibit formatting when writing to something that is a TTY.
This was something that I wasn't aware of (read as had no experience in) and I wasn't even aware of the fact that cin/cout can be redirected elsewhere. So I tried to read more about it and found some existing questions on SO too. Here's what I've hacked together :
// initialize them at start of program - mandatory
std::streambuf const *coutbuf = std::cout.rdbuf();
std::streambuf const *cerrbuf = std::cerr.rdbuf();
std::streambuf const *clogbuf = std::clog.rdbuf();
// ignore this, just checks for TERM env var
inline bool supportsColor()
{
if(const char *env_p = std::getenv("TERM")) {
const char *const term[8] = {
"xterm", "xterm-256", "xterm-256color", "vt100",
"color", "ansi", "cygwin", "linux"};
for(unsigned int i = 0; i < 8; ++i) {
if(std::strcmp(env_p, term[i]) == 0) return true;
}
}
return false;
}
rightTerm = supportsColor();
// would make necessary checks to ensure in terminal
inline bool isTerminal(const std::streambuf *osbuf)
{
FILE *currentStream = nullptr;
if(osbuf == coutbuf) {
currentStream = stdout;
}
else if(osbuf == cerrbuf || osbuf == clogbuf) {
currentStream = stderr;
}
else {
return false;
}
return isatty(fileno(currentStream));
}
// this would print checking rightTerm && isTerminal calls
inline std::ostream &operator<<(std::ostream &os, rang::style v)
{
std::streambuf const *osbuf = os.rdbuf();
return rightTerm && isTerminal(osbuf)
? os << "\e[" << static_cast<int>(v) << "m"
: os;
}
My main issue is, although I've tested this manually, I'm not aware of the cases this might fail or bugs it might contain. Is this the right way to do this thing? Is there anything I might be missing?
Here's a minimal example to get running (you'll also need a in.txt with random data):
#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
void f();
bool supportsColor();
// sample enum for foreground colors
enum class fg : unsigned char {
def = 39,
black = 30,
red = 31,
green = 32,
yellow = 33,
blue = 34,
magenta = 35,
cyan = 36,
gray = 37
};
// initialize them at start of program - mandatory
// so that even if user redirects, we've a copy
std::streambuf const *coutbuf = std::cout.rdbuf();
std::streambuf const *cerrbuf = std::cerr.rdbuf();
std::streambuf const *clogbuf = std::clog.rdbuf();
// check if TERM supports color
bool rightTerm = supportsColor();
// Here is the implementation of isTerminal
// which checks if program is writing to Terminal or not
bool isTerminal(const std::streambuf *osbuf)
{
FILE *currentStream = nullptr;
if(osbuf == coutbuf) {
currentStream = stdout;
}
else if(osbuf == cerrbuf || osbuf == clogbuf) {
currentStream = stderr;
}
else {
return false;
}
return isatty(fileno(currentStream));
}
// will check if TERM supports color and isTerminal()
inline std::ostream &operator<<(std::ostream &os, fg v)
{
std::streambuf const *osbuf = os.rdbuf();
return rightTerm && isTerminal(osbuf)
? os << "\e[" << static_cast<int>(v) << "m"
: os;
}
int main()
{
std::cout << fg::red << "ERROR HERE! " << std::endl
<< fg::blue << "ERROR INVERSE?" << std::endl;
std::ifstream in("in.txt");
std::streambuf *Orig_cinbuf = std::cin.rdbuf(); // save old buf
std::cin.rdbuf(in.rdbuf()); // redirect std::cin to in.txt!
std::ofstream out("out.txt");
std::streambuf *Orig_coutbuf = std::cout.rdbuf(); // save old buf
std::cout.rdbuf(out.rdbuf()); // redirect std::cout to out.txt!
std::string word;
std::cin >> word; // input from the file in.txt
std::cout << fg::blue << word << " "; // output to the file out.txt
f(); // call function
std::cin.rdbuf(Orig_cinbuf); // reset to standard input again
std::cout.rdbuf(Orig_coutbuf); // reset to standard output again
std::cin >> word; // input from the standard input
std::cout << word; // output to the standard input
return 0;
}
void f()
{
std::string line;
while(std::getline(std::cin, line)) // input from the file in.txt
{
std::cout << fg::green << line << "\n"; // output to the file out.txt
}
}
bool supportsColor()
{
if(const char *env_p = std::getenv("TERM")) {
const char *const term[8] = {"xterm", "xterm-256", "xterm-256color",
"vt100", "color", "ansi",
"cygwin", "linux"};
for(unsigned int i = 0; i < 8; ++i) {
if(std::strcmp(env_p, term[i]) == 0) return true;
}
}
return false;
}
I've also tagged c language although this is c++ code because the relevant code is shared b/w two and I don't want to miss any suggestions
OP's question:
My main issue is, although I've tested this manually, I'm not aware of the cases this might fail or bugs it might contain. Is this the right way to do this thing? Is there anything I might be missing?
Not all terminals support all features; in addition, the TERM variable is used most often to select a particular terminal description.
The usual approach to this is to use the terminal database rather than hard-coding things. Doing that, your methods
inline bool supportsColor()
inline std::ostream &operator<<(std::ostream &os, rang::style v)
would check the terminal capabilities, e.g., using tigetnum (for the number of colors), tigetstr (for the actual escape sequences which the terminal is supposed to support). You could just as easily wrap those as the isatty function.
Further reading:
interface to terminal database
terminal database
My terminal doesn't recognize color (ncurses FAQ)
To check on POSIX that the standard output is a terminal, just use isatty(3)
if (isatty(STDOUT_FILENO)) {
/// handle the stdout is terminal case
}
You might also use /dev/tty, see tty(4); e.g. if your program myprog is started in a command pipeline like ./myprog some arguments | less you could still fopen("/dev/tty","w") to output to the controlling terminal (even if stdout is then a pipe).
Sometimes, a program is run without any controlling terminal, e.g. thru crontab(5) or at(1)
I'm attempting to create a custom std::streambuf which acts as a sub-stream to a parent stream. This is an adaptation of the implementation outlined in this SO thread answer.
In this example below I am attempting to simply read the first 5 characters "Hello," of the stream. However, when I call ifstream.read() into the buffer, the buffer is filled with "ello, ", as though it is off by one.
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
stringstream ss;
ss << "Hello, World!";
substreambuf asd(ss.rdbuf(), 0, 5);
istream istream(&asd);
char buffer[6] = { '\0' };
istream.read(buffer, sizeof(buffer));
cout << buffer << endl; //prints "ello, "
}
I am new to streambufs and I feel like I'm missing something obvious here. Any help would be appreciated.
Here is the definition for substreambuf:
#include <iostream>
#include <sstream>
namespace std
{
struct substreambuf : public streambuf
{
explicit substreambuf(streambuf* sbuf, streampos pos, streampos len) :
m_sbuf(sbuf),
m_pos(pos),
m_len(len),
m_read(0)
{
m_sbuf->pubseekpos(pos);
setbuf(nullptr, 0);
}
protected:
int underflow()
{
if (m_read >= m_len)
{
return traits_type::eof();
}
return m_sbuf->sgetc();
}
int uflow()
{
if (m_read >= m_len)
{
return traits_type::eof();
}
m_read += 1;
return m_sbuf->snextc();
}
streampos seekoff(streamoff off, ios_base::seekdir seekdir, ios_base::openmode openmode = ios_base::in | ios_base::out)
{
if (seekdir == ios_base::beg)
{
off += m_pos;
}
else if (seekdir == ios_base::cur)
{
off += m_pos + m_read;
}
else if (seekdir == ios_base::end)
{
off += m_pos + m_len;
}
return m_sbuf->pubseekpos(off, openmode) - m_pos;
}
streampos seekpos(streampos streampos, ios_base::openmode openmode = ios_base::in | ios_base::out)
{
streampos += m_pos;
if (streampos > m_pos + m_len)
{
return -1;
}
return m_sbuf->pubseekpos(streampos, openmode) - m_pos;
}
private:
streambuf* m_sbuf;
streampos m_pos;
streamsize m_len;
streampos m_read;
};
};
I thought this was strange when I first saw the code.
int uflow()
{
// ...
return m_sbuf->snextc();
}
Why is he returning the result of snextc()? The policy defined for uflow() is "return the next available character and advance the input stream by one character". If snextc() is called, the input sequence will be advanced, and then it will return the next character. The result is that at least 1 character is skipped.
The correct method to call would be sbumpc() because it will cache the next character first, advance the input stream, and then return it.
return m_sbuf->sbumpc();
Here is a demo.
I have a file which is similar to /etc/passwd (semi-colon separated values), and need to extract all three values per line into variables then compare them to what have been given into the program. Here is my code:
typedef struct _UserModel UserModel;
struct _UserModel {
char username[50];
char email[55];
char pincode[30];
};
void get_user(char *username) {
ifstream io("test.txt");
string line;
while (io.good() && !io.eof()) {
getline(io, line);
if (line.length() > 0 && line.substr(0,line.find(":")).compare(username)==0) {
cout << "found user!\n";
UserModel tmp;
sscanf(line.c_str() "%s:%s:%s", tmp.username, tmp.pincode, tmp.email);
assert(0==strcmp(tmp.username, username));
}
}
}
I can't strcmp the values as the trailing '\0' mean the strings are different, so the assertion fails. I only really want to hold the memory for the values anyway and not use up memory that I don't need for these values. What do I need to change to get this to work..?
sscanf is so C'ish.
struct UserModel {
string username;
string email;
string pincode;
};
void get_user(char *username) {
ifstream io("test.txt");
string line;
while (getline(io, line)) {
UserModel tmp;
istringstream str(line);
if (getline(str, tmp.username, ':') && getline(str, tmp.pincode, ':') && getline(str, tmp.email)) {
if (username == tmp.username)
cout << "found user!\n";
}
}
}
If you are using c++, I would try to use std::string, iostreams and all those things that come with C++, but then again...
I understand that your problem is that one of the C strings is null terminated, while the other is not, and then the strcmp is stepping to the '\0' on one string, but the other has another value... if that is the only thing you want to change, use strncpy with the length of the string that is known.
Here's a complete example that does what I think you asked about.
Things you didn't ask for but it does anyway:
It uses exceptions to report data file format errors so that GetModelForUser() can simply return an object (instead of a boolean or something like that).
It uses a template function for splitting the line into fields. This really the heart of the original question and so it's a bit unfortunate that this is arguably over-complex. But the idea here of making it a template function is that this separates the concerns of splitting a string into fields from choosing a data structure to represent the result.
/* Parses a file of user data.
* The data file is of this format:
* username:email-address:pincode
*
* The pincode field is actually one-way-encrypted with a secret salt
* in order to avoid catastrophic loss of customer data when the file
* or a backup tape is lost/leaked/compromised. However, this code
* simply treats it as an opaque value.
*
* Internationalisation: this code assumes that the data file is
* encoded in the execution character set, whatever that is. This
* means that updates to the file must first transcode the
* username/mail-address/pincode data into the execution character
* set.
*/
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <iterator>
#include <exception>
const char* MODEL_DATA_FILE_NAME = "test.txt";
// This stuff should really go in a header file.
class UserUnknown : public std::exception { };
class ModelDataIsMissing : public std::exception { };
class InvalidModelData : public std::exception { }; // base: don't throw this directly.
class ModelDataBlankLine : public InvalidModelData { };
class ModelDataEmptyUsername : public InvalidModelData { };
class ModelDataWrongNumberOfFields : public InvalidModelData { };
class UserModel {
std::string username_;
std::string email_address_;
std::string pincode_;
public:
UserModel(std::string username, std::string email_address, std::string pincode)
: username_(username), email_address_(email_address), pincode_(pincode) {
}
UserModel(const UserModel& other)
: username_(other.username_),
email_address_(other.email_address_),
pincode_(other.pincode_) {
}
std::string GetUsername() const { return username_; }
std::string GetEmailAddress() const { return email_address_; }
std::string GetPincode() const { return pincode_; }
};
UserModel GetUserModelForUser(const std::string& username)
throw (InvalidModelData, UserUnknown, ModelDataIsMissing);
// This stuff is the implementation.
namespace { // use empty namespace for modularity.
template void SplitStringOnSeparator(
std::string input, char separator, ForwardIterator output)
{
std::string::const_iterator field_start, pos;
bool in_field = false;
for (pos = input.begin(); pos != input.end(); ++pos) {
if (!in_field) {
field_start = pos;
in_field = true;
}
if (*pos == separator) {
*output++ = std::string(field_start, pos);
in_field = false;
}
}
if (field_start != input.begin()) {
*output++ = std::string(field_start, pos);
}
}
}
// Returns a UserModel instance for the specified user.
//
// Don't call this more than once per program invocation, because
// you'll end up with quadratic performance. Instead modify this code
// to return a map from username to model data.
UserModel GetUserModelForUser(const std::string& username)
throw (InvalidModelData, UserUnknown, ModelDataIsMissing)
{
std::string line;
std::ifstream in(MODEL_DATA_FILE_NAME);
if (!in) {
throw ModelDataIsMissing();
}
while (std::getline(in, line)) {
std::vector<std::string> fields;
SplitStringOnSeparator(line, ':', std::back_inserter(fields));
if (fields.size() == 0) {
throw ModelDataBlankLine();
} else if (fields.size() != 3) {
throw ModelDataWrongNumberOfFields();
} else if (fields[0].empty()) {
throw ModelDataEmptyUsername();
} else if (fields[0] == username) {
return UserModel(fields[0], fields[1], fields[2]);
}
// We don't diagnose duplicate usernames in the file.
}
throw UserUnknown();
}
namespace {
bool Example (const char *arg)
{
const std::string username(arg);
try
{
UserModel mod(GetUserModelForUser(username));
std::cout << "Model data for " << username << ": "
<< "username=" << mod.GetUsername()
<< ", email address=" << mod.GetEmailAddress()
<< ", encrypted pin code=" << mod.GetPincode()
<< std::endl;
return true;
}
catch (UserUnknown) {
std::cerr << "Unknown user " << username << std::endl;
return false;
}
}
}
int main (int argc, char *argv[])
{
int i, returnval=0;
for (i = 1; i < argc; ++i)
{
try
{
if (!Example(argv[i])) {
returnval = 1;
}
}
catch (InvalidModelData) {
std::cerr << "Data file " << MODEL_DATA_FILE_NAME << " is invalid." << std::endl;
return 1;
}
catch (ModelDataIsMissing) {
std::cerr << "Data file " << MODEL_DATA_FILE_NAME << " is missing." << std::endl;
return 1;
}
}
return returnval;
}
/* Local Variables: /
/ c-file-style: "stroustrup" /
/ End: */
I don't see a problem with strcmp, but you have one in your sscanf format. %s will read upto the first non white character, so it will read the :. You probably want "%50[^:]:%55[^:]:%30s" as format string. I've added field size in order to prevent buffer overflow, but I could be off by one in the limit.