This is a main file that I am using to test methods before I implement them. I am trying to get the list of all files in a directory, write them to a txt file (It works fine until here), then read the file names from that text file.
using namespace std;
string sysCall = "", location = "~/Documents/filenames.txt";
string temp = "";
sysCall = "ls / > "+location;
system(sysCall.c_str());
ifstream allfiles(location.c_str());
allfiles.good();
getline(allfiles, temp);
cout<<temp<<endl; //At this point, the value of temp is equal to ""
return -1;
After the program runs, no text has been outputted. From what I've read in other peoples' questions, this should work (but obviously doesn't). What am I doing wrong here?
EDIT: allfiles.good() returns false, but I don't understand why it would return that...
ifstream allfiles("~/Documents/filenames.txt"); doesn't do what you think it does. The tilde ~ character is not part of the filename -- it is a special character interpreted by some shells. You need the entire path, with no ~ or $ characters in it.
Try setting location to "/tmp/filenames.txt", or just "filenames.txt".
Also, if Boost.Filesystem is available to you, you could use a directory_iterator instead of invoking /bin/ls.
I'll bet the system() call expands the ~ in the filename to your home directory (e.g. /home/mrswmmr), but ifstream does not. Replace the ~ with the full path to your home directory and it should work.
It has no guarantee to work because system gives no guarantee.
Related
This question already has an answer here:
C++ paths beginning with ~ [duplicate]
(1 answer)
Closed 3 years ago.
I have to open some file for writing, and its name contains the tilde sign (~). The following code fails to create the desired text file. If I replace the ~ with /home/oren then everything works fine.
#include <fstream>
#include <string>
const std::string dirname = "/home/oren/GIT/";
// const std::string dirname = "~/GIT/";
const std::string filename = "someTextFile";
int main(int argc, char **argv)
{
std::ofstream log_file(dirname+filename+".txt");
log_file << "lorem ipsum";
log_file.close();
}
Is there any way to (easily) handle a file with ~ in its name?
The ~ shortcut in paths is not something magical at the filesystem level, opening ~/GIT literally tries to access ~/GIT, i.e: a file named GIT in the ~ directory. You can verify this by creating ~ and GIT first.
In the command line, ~ is typically resolved by your shell. e.g: in bash:
~ : The value of $HOME
https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html
Therefore, to achieve the same effect, you have to query the $HOME envvar, and replace the usage of leading ~/ in the path:
#include <stdlib.h>
const char* home = getenv("HOME")
if (home) { /* replace ~/ with home */ }
In addition, on linux, the wordexp function can be used to perform these replacements (~ to current user, ~other_user to home of other user)
The tilde is part of the shell expansion, it's not something handled by the underlying operating system. You need to resolve it yourself.
One simple way is to replace leading "~/" with the contents of the environment variable HOME (if it exists).
The tilde is expanded to the home directory by the shell. The iostreams don't use a shell, so you have to take care of the expansion for them. Tilde is a actually a valid character to use in a file name so without expansion, a file is created into a directory named ~ - which fails if the directory does not exist.
There is no standard way in C++ for shell expansions, nor a way to get the home directory, but there are several ways in POSIX systems:
wordexp is probably one of the most useful functions for this case. You can pass the path to the function and it will expand the tilde, as well as variables and braces. An example:
std::string full = dirname+filename+".txt"
wordexp_t p;
wordexp(full.c_str(), &p, 0);
std::string expanded = p.we_wordv[p.we_offs];
wordfree(&p);
std::ofstream log_file(expanded);
Other alternatives:
getpwuid gives you a structure with the home directory as a member. This can be used to get home directory of another user as well, in case that is needed.
HOME environment variable should also be available. It can be accessed with the standard std::getenv.
I've written a simple program to copy files.
It gets two strings :
1) is for the path of the source file.
2) is for name of a copy file.
It works correctly when I give it the absolute or relative path(without tilde sign (~)).
But when I give it a relative path with tilde sign (~) it can't find the address of a file. And it makes me confused !
Here is my sample input :
1) /Users/mahan/Desktop/Copy.cpp
2) ~/Desktop/Copy.cpp
The first one works correctly but the second one no.
And here is my code :
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
string path, copy_name;
cin >> path >> copy_name;
ifstream my_file;
ofstream copy(copy_name);
my_file.open(path);
if(my_file.is_open())
{
copy << my_file.rdbuf();
copy.close();
my_file.close();
}
}
The ~ is handled by the shell you're using to auto expand to your $HOME directory.
std::ofstream doesn't handle the ~ character in the filepath, thus only your first sample works.
If you pass the filepath to your program from the command line using argv[1], and call it from your shell, you'll get the ~ automatically expanded.
With what was said above, if you want to expand the ~ character yourself, you can use the std::getenv() function to determine the value of $HOME, and replace it with that value.
The second example does not work because the shell is what replaces ~ with $HOME, i.e. the path to your home directory.
fstream objects will not perform this replacement and will instead look for a directory actually called ~, which likely does not exist in your working directory.
std::ofstream can't handle ~. It is a shortcut to your home directory. You need to give absolute path of home or the relative path with respect to the code run directory for it to work.
To give relative path, For example, if you are running your code in Desktop directory, then you needn't give ~/Desktop/Copy.cpp. Just give Copy.cpp and it should suffice.
Just like the title says, I've been working on a fairly large program and have come upon this bug. I'm also open to alternatives for searching a file for a string instead of using . Here is my code narrowed down:
istreambuf_iterator<char> eof;
ifstream fin;
fin.clear();
fin.open(filename.c_str());
if(fin.good()){
//I outputted text to a file to make sure opening the file worked, which it does
}
//term was not found.
if(eof == search(istreambuf_iterator<char>(fin), eof, term.begin(), term.end()){
//PROBLEM: this code always executes even when the string term is in the file.
}
So just to clarify, my program worked correctly in Linux but now that I have it in a win32 app project in vs2010, the application builds just fine but the search function isn't working like it normally did. (What I mean by normal is that the code in the if statement didn't execute because, where as now it always executes.)
NOTE: The file is a .xml file and the string term is simply "administration."
One thing that might or might not be important is to know that filename (filename from the code above) is a XML file I have created in the program myself using the code below. Pretty much I create an identical xml file form the pre-existing one except for it is all lower case and in a new location.
void toLowerFile(string filename, string newloc, string& newfilename){
//variables
ifstream fin;
ofstream fout;
string temp = "/";
newfilename = newloc + temp + newfilename;
//open file to read
fin.open(filename.c_str());
//open file to write
fout.open(newfilename.c_str());
//loop through and read line, lower case, and write
while (fin.good()){
getline (fin,temp);
//write lower case version
toLowerString(temp);
fout << temp << endl;
}
//close files
fout.close();
fin.close();
}
void toLowerString(string& data){
std::transform(data.begin(), data.end(), data.begin(), ::tolower);
}
I'm afraid your code is invalid - the search algorithm requires forward iterators, but istreambuf_iterator is only an input iterator.
Conceptually that makes sense - the algorithm needs to backtrack on a partial match, but the stream may not support backtracking.
The actual behaviour is undefined - so the implementation is allowed to be helpful and make it seem to work, but doesn't have to.
I think you either need to copy the input, or use a smarter search algorithm (single-pass is possible) or a smarter iterator.
(In an ideal world at least one of the compilers would have warned you about this.)
Generally, with Microsoft's compiler, if your program compiles and links a main() function rather than a wmain() function, everything defaults to char. It would be wchar_t or WCHAR if you have a wmain(). If you have tmain() instead, then you are at the mercy of your compiler/make settings and it's the UNICODE macro that determines which flavor your program uses. But I doubt that char_t/wchar_t mismatch is actually the issue here because I think you would have got an warning or error if all four of the search parameters didn't use the same the same character width.
This is a bit of a guess, but try this:
if(eof == search(istreambuf_iterator<char>(fin.rdbuf()), eof, term.begin(), term.end())
For example, I am calling my executable in ubuntu:
./foo temp/game1.txt temp/game2 txt
I am using realpath() to find the path to the game1.txt.
Using it however will give me the full path including the game1.txt name.
For example, it will come out as
/home/*/Download/temp/game1.txt
I want to erase game1.txt on that string so that I can use the string to use it to output other files in that folder.
Two questions.
Is realpath() alright to use for this kind of operation? Is there better way?
Can someone give me a quick way to erase "game1.txt" so that the string will be "/home/*/Download/temp/" save in a string format(not char)?
Thank you very much.
Don't really know Linux, but a general way for your second question:
#include <string>
#include <iostream>
int main(){
std::string fullpath("/home/*/Download/temp/game1.txt");
size_t last = fullpath.find_last_of('/');
std::string path = fullpath.substr(0,last+1);
std::cout << path;
}
See on Ideone.
You can use the dirname function for this: http://linux.die.net/man/3/dirname
Note that dirname will erase the trailing slash (except for the root directory); you'll have to append the slash back in when you append the filename.
Also, you don't actually need to use realpath for this purpose, you could simply use the argument as passed in, dirname will give you "temp" and you can append filenames to that.
man -S3 dirname
should do what you want
The cross-platform solution is
QFileInfo target_file_name(argv[1]);
QString absolute_path = target_file_name.absolutePath()
// e.g. /home/username/
QString some_other_file = QString("%1/another_file.txt").arg(absolute_path)
// => /home/username/another_file.txt
Boost.Filesystem can also do this easily. I just find the documentation of QFileInfo easily to navigate.
http://doc.qt.nokia.com/4.6/qfileinfo.html#absolutePath
Alright here's the deal, I'm taking an intro to C++ class at my university and am having trouble figuring out how to change the extension of a file. First, what we are suppose to do is read in a .txt file and count words, sentences, vowels etc. Well I got this but the next step is what's troubling me. We are then suppose to create a new file using the same file name as the input file but with the extension .code instead of .txt (in that new file we are then to encode the string by adding random numbers to the ASCII code of each character if you were interested). Being a beginner in programming, I'm not quite sure how to do this. I'm using the following piece of code to at first get the input file:
cout << "Enter filename: ";
cin >> filename;
infile.open(filename.c_str());
I'm assuming to create a new file I'm going to be using something like:
outfile.open("test.code");
But I won't know what the file name is until the user enters it so I can't say "test.txt". So if anyone knows how to change that extenstion when I create a new file I would very much appreciate it!
I occasionally ask myself this question and end up on this page, so for future reference, here is the single-line syntax:
string newfilename=filename.substr(0,filename.find_last_of('.'))+".code";
There are several approaches to this.
You can take the super lazy approach, and have them enter in just the file name, and not the .txt extension. In which case you can append .txt to it to open the input file.
infile.open(filename + ".txt");
Then you just call
outfile.open(filename + ".code");
The next approach would be to take the entire filename including extension, and just append .code to it so you'd have test.txt.code.
It's a bit ambiguous if this is acceptable or not.
Finally, you can use std::string methods find, and replace to get the filename with no extension, and use that.
Of course, if this were not homework but a real-world project, you'd probably do yourself -- as well as other people reading your code -- a favor by using Boost.Filesystem's replace_extension() instead of rolling your own. There's just no functionality that is simple enough that you couldn't come up with a bug, at least in some corner case.
Not to give it away since learning is the whole point of the exercise, but here's a hint.
You're probably going to want a combination of find_last_of and replace.
Here is a few hints. You have a filename already entered - what you want to do is get the part of the filename that doesn't include the extension:
std::string basename(const std::string &filename)
{
// fill this bit in
}
Having written that function, you can use it to create the name of the new file:
std::string codeFile = basename(filename) + ".code";
outFile.open(codeFile);
Pseudo code would be to do something like
outFilename = filename;
<change outFilename>
outfile.open(outFilename);
For changing outFilename, look at strrchr and strcpy as a starting point (might be more appropriate methods -- that would work great with a char* though)
In Windows (at least) you can use _splitpath to dissect the base name from the rest of the pieces, and then reassemble them using your favorite string formatter.
why not using the string method find_last_of() ?
std::string new_filename = filename;
size_type result = new_filename.find_last_of('.');
// Does new_filename.erase(std::string::npos) working here in place of this following test?
if (std::string::npos != result)
new_filename.erase(result);
// append extension:
filename.append(".code");
I would just append ".code" to the filename the user entered. If they entered "test.txt" then the output file would be "test.txt.code". If they entered a file name with no extension, like "test" then the output file would be "test.code".
I use this technique all the time with programs that generate output files and some sort of related logging/diagnostic output. It's simple to implement and, in my opinion, makes the relationships between files much more explicit.
How about using strstr:
char* lastSlash;
char* newExtension = ".code";
ChangeFileExtension(char* filename) {
lastSlash = strstr(filename, ".");
strcpy(lastSlash, newExtension);
}
What you'll need to do is copy the original filename into a new variable where you can change the extension. Something like this:
string outFilename;
size_t extPos = filename.rfind('.');
if (extPos != string::npos)
{
// Copy everything up to (but not including) the '.'
outFilename.assign(filename, 0, extPos);
// Add the new extension.
outFilename.append(".code");
// outFilename now has the filename with the .code extension.
}
It's possible you could use the "filename" variable if you don't need to keep the original filename around for later use. In that case you could just use:
size_t extPos = filename.rfind('.');
if (extPos != string::npos)
{
// Erase the current extension.
filename.erase(extPos);
// Add the new extension.
filename.append(".code");
}
The key is to look at the definition of the C++ string class and understand what each member function does. Using rfind will search backwards through the string and you won't accidentally hit any extensions in folder names that might be part of the original filename (e.g. "C:\MyStuff.School\MyFile.txt"). When working with the offsets from find, rfind, etc., you'll also want to be careful to use them properly when passing them as counts to other methods (e.g. do you use assign(filename, 0, extPos-1), assign(filename, 0, extPos), assign(filename, 0, extPos+1)).
Hope that helps.
size_t pos = filename.rfind('.');
if(pos != string::npos)
filename.replace(pos, filename.length() - pos, ".code");
else
filename.append(".code");
Very Easy:
string str = "file.ext";
str[str.size()-3]='a';
str[str.size()-2]='b';
str[str.size()-1]='c';
cout<<str;
Result:
"file.abc"