C++: Rename all files in a directory - c++

List of whats been achieved and what I'm stuck on to help with understanding what I am asking
What I have achieved:
Open a user specified directory, display all files within this directory.
What I haven't yet achieved:
Rename all files within this directory automatically according to a predefined name - Files are currently named as random characters, I wish to automatically rename them to "August 1", "August 2", "August 3" etc. Files have different extensions though, and I wish the extensions to remain the same.
So this is how I am opening and displaying the directory:
void DirectorySelector::OpenDirectory(void)
{
// convert directory string to const char
DIRECTORY = directory.c_str();
pdir = opendir (DIRECTORY);
}
void DirectorySelector::DisplayDirectory(void)
{
// read directory
while (pent = readdir (pdir))
{
std::cout << pent->d_name << "\n";
}
}
And this is what I am stuck on, renaming the files (files have different extensions, not sure if this will cause problems later on?)
I get the following error as soon as the program hits the while loop:
Unhandled exception at 0x009657C1 in MultipleRename.exe: 0xC0000005: Access violation reading location 0xCCCCCDE0.
void DirectoryOperator::StandardRename(void)
{
i = 1;
while (pent = readdir (pdir))
{
oldname = pent->d_name;
newname = "August " + i;
OLDNAME = oldname.c_str();
NEWNAME = newname.c_str();
rename(OLDNAME, NEWNAME);
i++;
}
}
Note: All declarations handled elsewhere and have removed validation for simplicity, if you need the code I can post it.
Also I have already checked that the directory is still open in the DirectoryOperator class and I am using MSVS2012 on Windows.
Thanks in advance.

There is a problem with the line:
newname = "August " + i;
"August " is a char* and i is added to the pointer before it is converted into a std::string.
So, when i==1, your string will be "ugust ", and when it is 2, it will be "gust ". Very quickly, when i > 8, you will run into undefined behavior.
Solutions:
newname = "August " + std::to_string(i); // c++11
or
#include<sstream>
...
stringstream ss;
ss << "August " << i;
newname = ss.str();

"I get the following error as soon as the program hits the while loop:"
Unhandled exception at 0x009657C1 in MultipleRename.exe: 0xC0000005: Access violation reading location 0xCCCCCDE0.
Most probably pdir isn't correctly initialized when the code
while (pent = readdir (pdir))
is called. The value 0xC0000005 indicates you're trying to dereference a nullptr somewhere.
Are you sure, that
pdir = opendir (DIRECTORY);
was called in sequence as intended, and the result was valid (pdir != nullptr)?

Related

How to move file in Linux using C++

How do you move a file from one location to another using a C++ program in Linux?
I've written a program to do this and it runs, but when I try to move a file to a different directory it doesn't move the file, I get the error message from the cout statement. When I try to just rename the file, moving it to the same directory with a new name it works. How can I fix my code so it will be able to move files to another directory?
Here's the code I've written:
#include <iostream>
#include <stdio.h>
using namespace std;
int main ()
{
int result=1;
char oldname[500];
char newname[500];
cout << "Enter the name of a file you want to move (include directory structure)";
cin >> oldname;
cout << "Enter the new location (include directory structure)";
cin >> newname;
result = rename( oldname , newname );
if ( result == 0 )
cout << "File successfully moved" << endl;
else
cout << "Error moving file" << endl;
return 0;
}
Edit:
I added perror to my code and the error message displayed is "Error moving file: No such file or directory" even though the directory I tried moving it to does exist and it has create and delete files permissions.
Your code will work in most cases. But you are ignoring some important things in which case it will break :
The obvious things like permissions, non-existing path, ...
Paths of 500 chars or more. Don't use static allocated memory for oldname and newname
Moving between filesystems is not possible with rename() so do it like this ( and include iostream )
ifstream ifs(oldname, ios::in | ios::binary);
ofstream ofs(newname, ios::out | ios::binary);
ofs << ifs.rdbuf();
remove(oldname);
Before the remove() your total disk space will be a bit less.
This doesn't matter if your are moving between filesystems because only the free space on the filesystem with newname will shrink and this is free space you have because otherwise you wouldn't able to move the file here
If oldname and newname are on the same filesystem and you really care about this temporary loss then check whether you'll be using the same filesystem and use rename() after all.
How to fix your program depends on the reason why the move (rename) failed.
The reason for the failure can be found using errno.
In this case, it was necessary to make sure that the source file exists.
For all things that need to be considered to robustly implement moving, I recommend studying an implementation of mv command.

C++ Merge 2 Strings Together

I'm trying to create a simple text file to Desktop, but I get in the console : "Access is denied.". I do the same thing from the Comand Line and no error. If I where to print the path I would get :
"
C:/Users/Alex
(Here is a new line)
/Desktop/MyTextFile.txt
I know when I add a string to another string via += string, I get a space between the 2 strings. Any help would be apreciate !
string getClip() {
HANDLE clip;
string clip_text = "";
if (OpenClipboard(NULL))
{
clip = GetClipboardData(CF_TEXT);
clip_text = (char*)clip;
CloseClipboard();
return clip_text;
}
}
string getUser() {
system("echo %username% | clip");
string user = getClip();
return user;
}
void create_a_simple_txt_file() {
string username = getUser();
ostringstream path;
path << "C:/Users/" << username << "/Desktop/MyTextFile.txt";
system(("echo I successful Write something to my file > " + path.str()).c_str());
}
int main() {
create_a_simple_txt_file();
cin.get();
return 0;
}
The problem is the usage of system and the echo command:
system("echo %username% | clip");
The echo command adds a trailing newline, which is copied into the clipboard with the clip command, and you then get the full string including the newline with getClip.
You could simply fix this by not using system at all, and instead get the username with the GetUserName Windows API function:
std::string getUser()
{
char username[64];
// Use the ASCII version of the function
if (GetUserNameA(username, sizeof username))
{
return username;
}
// An error
std::cerr << "Error getting username: " << GetLastError() << '\n';
return "";
}
I would bet your problem is in the getUser() function, that is returning the username with a trailing newline, not the string concatenation (that, by the way, does NOT add a space).
EDITED after question being edited:
As I supposed, the problem is in your getUser() function, see "Some programmer dude" answer about why it is wrong and a possible solution, if you are running on Windows OS.
Another problem can be that you are trying to access the desktop folder of another user, and the user running your program does not have permissions on it.
Another suggestion: you may want to use the appropriate C++ functions or fstream to write to a file, instead of using system().
You are getting an access denied error because you are working on garbage data. The line
system(("echo I successful Write something to my file > " + path.str()).c_str());
creates a temporary string and gets a pointer to its underlying character array. But before the system() call is executed, the temporary string gets destroyed and the pointer becomes invalid.
If you replace your system call with a cout you can see what command you are actually trying to execute. For me it was something like Ó╣iwµ, which is neither a valid command not a valid directory name. Hence, the access denied.
To solve that problem, assign the temporary string to a variable until you have used it:
string s = ("echo I successful Write something to my file > " + path.str());
system(s.c_str());

Using dirent->d_name together with string fails

I'm writing an C++ Application that uses the dirent.h library, to read files from a directory. At one point I want to decide between Files and directories. To achieve that I added the following piece of code:
entry = readdir(used_directory); //read next object from directory stream
DIR* directory_test = opendir((path + entry->d_name).c_str()); //try to open object as directory
if ( directory_test != nullptr) { //object is directory
if (entry != nullptr) { //reading from directory succeeded
dirs.push_back(entry->d_name); //add filename to file list
++dircounter;
}
}
else { //object is file
path is type of string and entry is type of dirent *.
With this, the program causes an memory access error, without it doesn't.
I figured out, that the error is caused by the
(path + entry->d_name)
But it is not the implicit conversion to string in the statement, because other tests like cout << entry->d_name; or path += entry->d_name failed with the same error, too. So obviously there is a failure with using entry->d_name as char *, although it is defined so (in the documentation of dirent.h).
Why is this failure occuring?
EDIT:
Later in the program I add entry->d_name to a vector<string>, that doesn't cause any problems.
The failure was accessing entry before checking if it's equal to nullptr.
Because my loop itterating through the directory is stopped if entry is equal to nullptr, the last itteration causes the error.

Create a file at a given path using C++ in Linux

I want to create a file at a given path that is relative to the current directory. The following code behaves erratically. I some times see the file created and some times do not. That may be because of the change in the current directory. Here's the code.
//for appending timestamp
timeval ts;
gettimeofday(&ts,NULL);
std::string timestamp = boost::lexical_cast<std::string>(ts.tv_sec);
//./folder/inner_folder is an existing directory
std::string filename = "./folder/inner_folder/abc_"+timestamp+ ".csv";
std::ofstream output_file(filename);
output_file << "abc,efg";
output_file.close();
Now, the problem is the file is created only in some cases. That is when I have as a command line argument an input file from the current directory, it works fine.
./program input_file
If I have something like this, it does not work
./program ./folder1/input_file
I tried giving the full path as an argument for ofstream, I still don't see the files created.
What is the correct way to do this? Thanks
ofstream will not create missing directories in the file path, you must ensure the directories exist and if not create them using OS specific api or boost's file system library.
Always check the result of IO operations, and query system error code to determine reason for failures:
if (output_ file.is_open())
{
if (!(output_file << "abc,efg"))
{
// report error.
}
}
else
{
const int last_error = errno;
std::cerr << "failed to open "
<< filename
<< ": "
<< strerror(last_error)
<< std::endl;
}

readdir(): re-reading certain files

I got a function which task is to rename all files in a folder however, it re-rename certain files:
http://i.imgur.com/JjN8Qb2.png, the same kind of "error" keeps occurring for every tenth number onwards. What exactly is causing this "error"?
The two arguments to the function is the path for the folder and what start value the first file should have.
int lookup(std::string path, int *start){
int number_of_chars;
std::string old_s, file_format, new_s;
std::stringstream out;
DIR *dir;
struct dirent *ent;
dir = opendir (path.c_str());
if (dir != NULL) {
// Read pass "." and ".."
ent = readdir(dir);
ent = readdir(dir);
// Change name of all the files in the folder
while((ent = readdir (dir)) != NULL){
// Old string value
old_s = path;
old_s.append(ent->d_name);
// Get the format of the image
file_format = ent->d_name;
number_of_chars = file_format.rfind(".");
file_format.erase(0,number_of_chars);
// New string value
new_s = path;
out << *start;
new_s += out.str();
new_s.append(file_format);
std::cout << "Successfully changed name on " << ent->d_name << "\tto:\t" << *start << file_format << std::endl;
// Switch name on the file from old string to new string
rename(old_s.c_str(), new_s.c_str());
out.str("");
*start = *start+1;
}
closedir (dir);
}
// Couldn't open
else{
std::cerr << "\nCouldn't open folder, check admin privileges and/or provided file path\n" << std::endl;
return 1;
}
return 0;
}
You are renaming files to the same folder in which the original files were, resulting in an infinite loop. You renamed 04.png to 4.png but since you are iterating over all files in the folder, at some point you're going to iterate to the "new" 4.png file (in your smaple, on the 40th iteration) and rename that file to 40.png and so on...
The easiest way to resolve this with minimal changes to the existing code is to "rename" (move) the files to a temporary folder with their new names. Something like:
new_s = temp_path;
out << *start;
new_s += out.str();
new_s.append(file_format);
// Switch name on the file from old string to new string
rename(old_s.c_str(), new_s.c_str());
and when you are done renaming all the files in path (outside the while loop), delete the folder and "rename" (move) temp_path to `path:
closedir (dir);
deletedir(path);
rename(temp_path, path);
`
Possible problems I see:
Renaming files causes them to be fed to your algorithm twice.
Your algorithm for computing the new filename is wrong.
You should be able to write a test for this easily, which in turn should help you fix the problem or write a more specific question. Other than that, I don't see any grave issues, but it would help if you reduced the scope of variables a bit, which would make sure that different iterations don't influence each other.