The array itself is held at
char filestring[9];
and initialized via
snprintf_P(filestring,
sizeof(filestring),
PSTR("%04u%02u%02u"),
dt.Year(),
dt.Month(),
dt.Day());
How can i concatenate all above as in the example? (Add the slash and the .txt extension to the filestring variable)
File file = SD.open("/" + filestring + ".txt", FILE_APPEND);
I get the following misleading error for the example above.
expression must have integral or unscoped enum type
Maybe something like this:
char filename[MAX_PATH] = {0};
int n = snprintf(filename, sizeof(filename), "/%s.txt", filestring);
// check whether snprintf succeeded
if (n > 0 && n < sizeof(filename)) {
File file = SD.open(filename, FILE_APPEND);
}
Update: As requested by a user I am adding a clarification on MAX_PATH:
The line
char filename[MAX_PATH] = {0};
Defines a character array of size MAX_PATH. That could have used any integer value that you thought right for your program but, using MAX_PATH ensures the buffers can hold any filename.
On Linux, you must #include <limits.h> (or you can #include <stdio.h> and use FILENAME_MAX). I am not a Windows user but it looks like you have to #include <stdlib.h> to import MAX_PATH (doc).
Of course you could also also initialized filestring with the desired format in one go:
char filestring[MAX_PATH];
snprintf_P(filestring,
sizeof(filestring),
PSTR("/%04u%02u%02u.txt"),
dt.Year(),
dt.Month(),
dt.Day());
In C:
const int size = MAX_PATH;
char path[size];
int rc = snprintf(path, size, "/%s.txt", filestring);
if (rc < 0) {
fprintf(stderr, "Concatenation error.\n");
} else if (rc > size) {
fprintf(stderr, "Buffer is too small.\n");
} else {
printf("path: %s\n", path);
// Use it...
}
In C++ (since you tagged your question C++):
std::string path = "/" + std::string(filestring) + ".txt";
File file = SD.open(path.c_str(), FILE_APPEND);
Here's an alternative using a std::ostringstream to build the filename and a std::string to pass the result around to other functions:
#include <iomanip>
#include <sstream>
#include <string>
void some_function() {
std::ostringstream os;
// build the string using the std::ostringstream
os << std::setfill('0')
<< '/'
<< std::setw(4) << dt.Year()
<< std::setw(2) << dt.Month()
<< std::setw(2) << dt.Day()
<< ".txt";
// extract the result into a std::string
std::string filestring(os.str());
// Then depending on the SD.open() interface:
// 1. The preferred:
File file = SD.open(filestring, FILE_APPEND);
// 2. Backup version:
File file = SD.open(filestring.c_str(), FILE_APPEND);
}
Related
I need to remove a file from a directory based on the input of user and pass it into a function that perform the file remover process
/* Class 3 veus 3:45PM*/
#include <string>
#include <iostream>
#include <stdio.h>
#include <cstdio>
void remove_file(std::string file);
int main() {
std::string file_name;
std::cin >> file_name;
remove_file(file_name);
}
void remove_file(std::string file) {
if(remove("C:\\MAIN_LOC\\" + file + ".txt") == 0) {
std::cout << "`" << file << "`" << " Item deleted successfully" << std::endl;
} else {
std::cout << "[Error] File not found";
}
}
Ok now the thing is I got several error on remove function: function "remove" cannot be called with the given argument list. I'm not sure what the error mean so I'd like for an explanation.
remove takes a C string, but your expression "C:\\MAIN_LOC\\" + file + ".txt" is a C++ string. Use the c_str method to convert to a C string
remove(("C:\\MAIN_LOC\\" + file + ".txt").c_str())
use the function with c string instead:
void remove_file(std::string file)
{
std::string x = "C:\\MAIN_LOC\\" + file + ".txt";
if(remove(x.c_str()) == 0)
{
....
}
I'm using small files currently for testing and will scale up once it works.
I made a file bigFile.txt that has:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
I'm running this to segment the data that is being read from the file:
#include <iostream>
#include <fstream>
#include <memory>
using namespace std;
int main()
{
ifstream file("bigfile.txt", ios::binary | ios::ate);
cout << file.tellg() << " Bytes" << '\n';
ifstream bigFile("bigfile.txt");
constexpr size_t bufferSize = 4;
unique_ptr<char[]> buffer(new char[bufferSize]);
while (bigFile)
{
bigFile.read(buffer.get(), bufferSize);
// print the buffer data
cout << buffer.get() << endl;
}
}
This gives me the following result:
26 Bytes
ABCD
EFGH
IJKL
MNOP
QRST
UVWX
YZWX
Notice how in the last line after 'Z' the character 'WX' is repeated again?
How do I get rid of it so that it stops after reaching the end?
cout << buffer.get() uses the const char* overload, which prints a NULL-terminated C string.
But your buffer isn't NULL-terminated, and istream::read() can read less characters than the buffer size. So when you print buffer, you end up printing old characters that were already there, until the next NULL character is encountered.
Use istream::gcount() to determine how many characters were read, and print exactly that many characters. For example, using std::string_view:
#include <iostream>
#include <fstream>
#include <memory>
#include <string_view>
using namespace std;
int main()
{
ifstream file("bigfile.txt", ios::binary | ios::ate);
cout << file.tellg() << " Bytes" << "\n";
file.seekg(0, std::ios::beg); // rewind to the beginning
constexpr size_t bufferSize = 4;
unique_ptr<char[]> buffer = std::make_unique<char[]>(bufferSize);
while (file)
{
file.read(buffer.get(), bufferSize);
auto bytesRead = file.gcount();
if (bytesRead == 0) {
// EOF
break;
}
// print the buffer data
cout << std::string_view(buffer.get(), bytesRead) << endl;
}
}
Note also that there's no need to open the file again - you can rewind the original one to the beginning and read it.
The problem is that you don't override the buffer's content. Here's what your code does:
It reads the beginning of the file
When reaching the 'YZ', it reads it and only overrides the buffer's first two characters ('U' and 'V') because it has reached the end of the file.
One easy fix is to clear the buffer before each file read:
#include <iostream>
#include <fstream>
#include <array>
int main()
{
std::ifstream bigFile("bigfile.txt", std::ios::binary | std::ios::ate);
int fileSize = bigFile.tellg();
std::cout << bigFile.tellg() << " Bytes" << '\n';
bigFile.seekg(0);
constexpr size_t bufferSize = 4;
std::array<char, bufferSize> buffer;
while (bigFile)
{
for (int i(0); i < bufferSize; ++i)
buffer[i] = '\0';
bigFile.read(buffer.data(), bufferSize);
// Print the buffer data
std::cout.write(buffer.data(), bufferSize) << '\n';
}
}
I also changed:
The std::unique_ptr<char[]> to a std::array since we don't need dynamic allocation here and std::arrays's are safer that C-style arrays
The printing instruction to std::cout.write because it caused undefined behavior (see #paddy's comment). std::cout << prints a null-terminated string (a sequence of characters terminated by a '\0' character) whereas std::cout.write prints a fixed amount of characters
The second file opening to a call to the std::istream::seekg method (see #rustyx's answer).
Another (and most likely more efficient) way of doing this is to read the file character by character, put them in the buffer, and printing the buffer when it's full. We then print the buffer if it hasn't been already in the main for loop.
#include <iostream>
#include <fstream>
#include <array>
int main()
{
std::ifstream bigFile("bigfile.txt", std::ios::binary | std::ios::ate);
int fileSize = bigFile.tellg();
std::cout << bigFile.tellg() << " Bytes" << '\n';
bigFile.seekg(0);
constexpr size_t bufferSize = 4;
std::array<char, bufferSize> buffer;
int bufferIndex;
for (int i(0); i < fileSize; ++i)
{
// Add one character to the buffer
bufferIndex = i % bufferSize;
buffer[bufferIndex] = bigFile.get();
// Print the buffer data
if (bufferIndex == bufferSize - 1)
std::cout.write(buffer.data(), bufferSize) << '\n';
}
// Override the characters which haven't been already (in this case 'W' and 'X')
for (++bufferIndex; bufferIndex < bufferSize; ++bufferIndex)
buffer[bufferIndex] = '\0';
// Print the buffer for the last time if it hasn't been already
if (fileSize % bufferSize /* != 0 */)
std::cout.write(buffer.data(), bufferSize) << '\n';
}
So, I want to create a log file for an app I am trying to create and I don't know how to name the log to something like "log/date&time"
Anyway, here is my code:
#include <iostream>
#include <fstream>
#include <time.h>
#include <stdio.h>
#include <sstream>
using namespace std;
int main (int argc, char *argv[])
{
time_t t = time(0);
struct tm * now = localtime( & t );
char buffer [80];
strftime (buffer,80,"%Y-%m-%d.",now); //i think i can't just put "log/%Y-%m-%d." there.
ofstream myfile;
myfile.open ("log/" + buffer); // this is my problem, i can't put the ' "log/" + ' part there
if(myfile.is_open())
{
cout<<"Success"<<std::endl;
}
return 0;
}
You should use std::string which supports concatenation via the overloaded operator+.
std::string buffer(80, '\0');
strftime( &buffer[0], buffer.size(), "some format string", now);
/* ... */
std::ofstream myfile( ("log/" + buffer).c_str() );
// Remove the (..).c_str() part when working with a C++11 conforming
// standard library implementation
you actual question is "why doesnt this work"
myfile.open ("log/" + buffer);
answer - because c++ doesnt support what you want - concatenate a string literal with a char * and return another char *.
do
std::string filetime(buffer);
std::string filename = "log/" + filetime;
open(filename.c_str());
Consider using std:: facilities instead (std::string and std::ostringstream come to mind):
std::ostream& time_digits(std::ostream& out, unsigned int digits)
{ // convenience function: apply width and fill for the next input
return out << std::setw(digits) << std::setfill('0');
}
std::string unique_log_name()
{ // generate unique log name, depending on local time
// example output: "log/2014-04-19.log"
auto now = time(0);
tm *ltm = localtime(&now);
std::ostringstream buffer;
buffer
<< "log/" << time_digits(4) << ltm.tm_year
<< "-" << time_digits(2) << ltm.tm_mon
<< "-" << time_digits(2) << ltm.tm_day;
// could also add these to the name format:
// buffer
// << "-" << time_digits(2) << ltm.dm_hour
// << "-" << time_digits(2) << ltm.tm_min
// << "-" << time_digits(2) << ltm.tm_sec;
buffer << ".log"; // add extension
return buffer.str();
}
void client_code()
{ // construct log stream on unique file name
ofstream myfile{ unique_log_name() };
if(myfile)
{
cout << "Success" << std::endl;
}
}
I am writing a simple program that builds a directory index of the current directory.
Each file has two char* objects for file name and last-modified time, and one integer for the file size.
I want to put all these in one big string or char*.
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <stdio.h>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
using namespace std;
char* file_info(char*);
int main(void)
{
DIR *d;
struct dirent *dir;
d = opendir(".");
if (d)
{
while ((dir = readdir(d)) != NULL)
{
file_info(dir->d_name);
}
closedir(d);
}
return(0);
}
char* file_info(char* file) {
if(file[0] != '.') {
struct stat sb;
if (stat(file, &sb) == -1) {
perror("stat");
exit(EXIT_FAILURE);
}
char* lm = ctime(&sb.st_mtime);
*lm = '\0';
stringstream ss;
ss << file << " " << lm << " " << sb.st_size;
cout << ss.str() << endl;
}
return lm;
}
I want the returned char* to be an object that has content in this format:
homework-1.pdf 12-Sep-2013 10:57 123K
homework-2.pdf 03-Oct-2013 13:58 189K
hw1_soln.pdf 24-Sep-2013 10:36 178K
hw2_soln.pdf 14-Oct-2013 09:37 655K
The spacing is the major issue here.
How can I correct it easily?
My attempt so far was
const char* file_info(char* file) {
if(file[0] != '.') {
struct stat sb;
if (stat(file, &sb) == -1) {
perror("stat");
exit(EXIT_FAILURE);
}
char* lm = ctime(&sb.st_mtime);
string lastmod(lm);
lastmod.at(lastmod.size()-1) = '\0';
stringstream ss;
string spacing = " ";
ss << file << spacing.substr(0, spacing.size() - sizeof(file)) << lastmod << spacing.substr(0, spacing.size() - lastmod.size()) << sb.st_size;
cout << ss.str() << endl;
return ss.str().c_str();
}
else {
return NULL;
}
}
but it did not work, and I worked with strings so poorly.
Here is the problem:
// ...
stringstream ss;
// ...
return ss.str().c_str(); // woops! ss goes out of scope and string will be destroyed!
This can be easily solved by making your function return std::string instead of char const* and doing this:
return ss.str();
There is no reason to return char const* here. It complicates everything, requires manual memory management, will be exception-unsafe at some point, confuses people who call your function and makes your code utterly unmaintainable.
To answer your iostream formatting question, you want std::setw
std::cout << "'" << std::setw(16) << "Hello" << "'" << std::endl;
http://faculty.cs.niu.edu/~mcmahon/CS241/c241man/node83.html
There are two different problems. First you obviously can not return const char * from function which is stack-allocated. So you have to have it allocated at the heap. And this is the problem. It is problem of ownership. Where you have to delete this string? It can be easily solved by using std::string.
Second problem is yours question. How to have this well aligned. Using yours method you can not print filenames longer then preallocated string. There si simple solution. In header iomanip is defined function
/*unspecified*/ std::setw( int n );
which say "Hey, next thing you will be printing have to be n characters long". And this is what you want. When thing you will be printing is longer then this n it will be printed all. No cropping or something like this.
If you absolutly have to use null-terminated C-Strings, than rather use sprintf instead of std::stringstream. Mixing C and C++ like this is considered bad practice (like pointed out already: i.e. you have to manage memory manually). Also there are some other issues with your code: the sizeof() operator doesn't calculate the length of a string - rather the necessary memory-space (in bytes). Returning a reference to the ctime internal buffer isn't safe either:
The function also accesses and modifies a shared internal buffer,
which may cause data races on concurrent calls to asctime or ctime
Rather use Call-by-reference and don't return anything. Like this:
void file_info(char* file, char* buffer) {
if(file[0] != '.') {
struct stat sb;
if (stat(file, &sb) == -1) {
perror("stat");
exit(EXIT_FAILURE);
}
char* lm = ctime(&sb.st_mtime);
*lm = '\0';
sprintf(buffer, "%10s%10s%d", file, lm, sb.st_size);
}
}
To fix your formating problem, you could also use strlen() (but not sizeof()) and use whitespaces depending on the length of lm and file. But sprintf provides an fixed length Parameter with %"number of digits"s.
See also: printf reference
Minimum number of characters to be printed. If the value to be printed
is shorter than this number, the result is padded with blank spaces.
The value is not truncated even if the result is larger.
But you would allocate memory for char* buffer before you call this function and have to make sure, it's large enough for the sprintf string(!).
i.e.
char buffer[256];
file_info(file, buffer);
printf("%s\n", buffer);
Thank you all for your answers.
However, none of them worked as I intended.(especially, it's not for outputting, but for making a string object.)
I ended up achieving what I wanted, but it's by no means good.
I attach my program, though, below. Feel free to comment.
Thank you.
void file_info(char*, stringstream&);
int main(void)
{
DIR *d;
struct dirent *dir;
d = opendir(".");
stringstream ss;
if (d)
{
while ((dir = readdir(d)) != NULL)
{
file_info(dir->d_name, ss);
}
closedir(d);
}
cout << ss.str() << endl;
return(0);
}
void file_info(char* file, stringstream& ss) {
if(file[0] != '.') {
struct stat sb;
if (stat(file, &sb) == -1) {
perror("stat");
exit(EXIT_FAILURE);
}
char* lm = ctime(&sb.st_mtime);
string lastmod(lm);
lastmod.at(lastmod.size()-1) = '\0';
string spacing = " ";
ss << file << spacing.substr(0, spacing.size() - strlen(file)) << lastmod << spacing.substr(0, spacing.size() - lastmod.size()) << sb.st_size << '\n';
}
return;
}
I am trying to grab the raw filename without the extension from the filename passed in arguments:
int main ( int argc, char *argv[] )
{
// Check to make sure there is a single argument
if ( argc != 2 )
{
cout<<"usage: "<< argv[0] <<" <filename>\n";
return 1;
}
// Remove the extension if it was supplied from argv[1] -- pseudocode
char* filename = removeExtension(argv[1]);
cout << filename;
}
The filename should for example be "test" when I passed in "test.dat".
size_t lastindex = fullname.find_last_of(".");
string rawname = fullname.substr(0, lastindex);
Beware of the case when there is no "." and it returns npos
This works:
std::string remove_extension(const std::string& filename) {
size_t lastdot = filename.find_last_of(".");
if (lastdot == std::string::npos) return filename;
return filename.substr(0, lastdot);
}
Since C++17 you can use std::filesystem::path::replace_extension with a parameter to replace the extension or without to remove it:
#include <iostream>
#include <filesystem>
int main()
{
std::filesystem::path p = "/foo/bar.txt";
std::cout << "Was: " << p << std::endl;
std::cout << "Now: " << p.replace_extension() << std::endl;
}
Compile it with:
g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Running the resulting binary leaves you with:
Was: "/foo/bar.txt"
Now: "/foo/bar"
However this does only remove the last file extension:
Was: "/foo/bar.tar.gz"
Now: "/foo/bar.tar"
In my opinion it is easiest, and the most readable solution:
#include <boost/filesystem/convenience.hpp>
std::string removeFileExtension(const std::string& fileName)
{
return boost::filesystem::change_extension(fileName, "").string();
}
For those who like boost:
Use boost::filesystem::path::stem. It returns the filename without the last extension. So ./myFiles/foo.bar.foobar becomes foo.bar. So when you know you are dealing with only one extension you could do the follwing:
boost::filesystem::path path("./myFiles/fileWithOneExt.myExt");
std::string fileNameWithoutExtension = path.stem().string();
When you have to deal with multiple extensions you might do the following:
boost::filesystem::path path("./myFiles/fileWithMultiExt.myExt.my2ndExt.my3rdExt");
while(!path.extension().empty())
{
path = path.stem();
}
std::string fileNameWithoutExtensions = path.stem().string();
(taken from here: http://www.boost.org/doc/libs/1_53_0/libs/filesystem/doc/reference.html#path-decomposition found in the stem section)
BTW works with rooted paths, too.
The following works for a std::string:
string s = filename;
s.erase(s.find_last_of("."), string::npos);
More complex, but with respect to special cases (for example: "foo.bar/baz", "c:foo.bar", works for Windows too)
std::string remove_extension(const std::string& path) {
if (path == "." || path == "..")
return path;
size_t pos = path.find_last_of("\\/.");
if (pos != std::string::npos && path[pos] == '.')
return path.substr(0, pos);
return path;
}
You can do this easily :
string fileName = argv[1];
string fileNameWithoutExtension = fileName.substr(0, fileName.rfind("."));
Note that this only work if there is a dot. You should test before if there is a dot, but you get the idea.
In case someone just wants a simple solution for windows:
Use PathCchRemoveExtension ->MSDN
... or PathRemoveExtension (deprecated!) ->MSDN
Try the following trick to extract the file name from path with no extension in c++ with no external libraries in c++ :
#include <iostream>
#include <string>
using std::string;
string getFileName(const string& s) {
char sep = '/';
#ifdef _WIN32
sep = '\\';
#endif
size_t i = s.rfind(sep, s.length());
if (i != string::npos)
{
string filename = s.substr(i+1, s.length() - i);
size_t lastindex = filename.find_last_of(".");
string rawname = filename.substr(0, lastindex);
return(rawname);
}
return("");
}
int main(int argc, char** argv) {
string path = "/home/aymen/hello_world.cpp";
string ss = getFileName(path);
std::cout << "The file name is \"" << ss << "\"\n";
}
Just loop through the list and replace the first (or last) occurrence of a '.' with a NULL terminator. That will end the string at that point.
Or make a copy of the string up until the '.', but only if you want to return a new copy. Which could get messy since a dynamically allocated string could be a source of memory leak.
for(len=strlen(extension);len>= 0 && extension[len] != '.';len--)
;
char * str = malloc(len+1);
for(i=0;i<len;i++)
str[i] = extension[i];
str[i] = '\0'l