Is there a smooth way to glob in C or C++ in Windows?
E.g., myprogram.exe *.txt sends my program an ARGV list that has...ARGV[1]=*.txt in it.
I would like to be able to have a function (let's call it readglob) that takes a string and returns a vector of strings, each containing a filename.
This way, if I have files a.txt b.txt c.txt in my directory and readglob gets an argument *.txt, it returns the above filelist.
//Prototype of this hypothetical function.
vector<string> readglob(string);
Does such exist?
Link with setargv.obj (or wsetargv.obj) and argv[] will be globbed for you similar to how the Unix shells do it:
http://msdn.microsoft.com/en-us/library/8bch7bkk.aspx
I can't vouch for how well it does it though.
This is very Windows-specific. I don't know how you'd write this to be cross-platform. But I've used this in Windows programs and it works well for me.
// Change to the specified working directory
string path;
cout << "Enter the path to report: ";
cin >> path;
_chdir(path.c_str());
// Get the file description
string desc;
cout << "Enter the file description: ";
cin >> desc;
// List the files in the directory
intptr_t file;
_finddata_t filedata;
file = _findfirst(desc.c_str(),&filedata);
if (file != -1)
{
do
{
cout << filedata.name << endl;
// Or put the file name in a vector here
} while (_findnext(file,&filedata) == 0);
}
else
{
cout << "No described files found" << endl;
}
_findclose(file);
there was talk about having it in Boost::filesystem but it was dropped in favor of using the boost::regex.
For win32 specific (MFC) you can use the CFileFind class
There may be a better way now, but last time I had to deal with this problem I ended up including Henry Spencer's regex library statically linked into my program (his library is BSD licensed), and then I made a wrapper class that converted the user's glob-expressions into regular expressions to feed to the regex code. You can view/grab the wrapper class here if you like.
Once you have those parts in place, the final thing to do is actually read the directory, and pass each entry name into the matching function to see if it matches the expression or not. The filenames that match, you add to your vector; the ones that don't you discard. Reading the directory is fairly straightforward to do using the DOS _findfirst() and _findnext() functions, but if you want a nicer C++ interface I have a portable wrapper class for that also...
Ehw. I had to implement something like this in ANSI C about 15 years ago. Start with the ANSI opendir/readdir routines, I guess. Globs aren't exactly RegExs, so you will have to implement your own filtering.
Related
I'm new to C++ programming, and I'm trying to practice file reading and writing. I'm trying to get the sizes of all the files of the current directory. Thing is, after getting the names of the files in the current directory, I place them inside of a text file. So now I'm stuck, and don't know where to go from here.
#include <iostream>
#include <fstream>
#include <algorithm>
using namespace std;
// FILE FUNCTION
void fileStuff(){
}
// MAIN FUNCTION
int main(int argc, char const *argv[])
{
// ERROR CHECKING
if(argc != 3){ // IF USER DOESN'T TYPE ./nameOfFile, AND THE OTHER REQUIRED ARGUMENTS.
cout << "Incorrect. Try Again" << endl;
exit(-1);
}
ifstream file;
string fileContents;
system("find . -type f > temp.txt");
file.open("temp.txt");
if (!file){
cout << "Unable to open file: temp.txt" << endl;
exit(-1);
}
while(file){
getline(file, fileContents);
cout << fileContents << endl;
}
file.close();
return 0;
}
C++14 (and earlier versions, notably C++11) does not know about file systems and directories (yet). For C++17, see its file system library. Otherwise, your code is operating system specific, but Boost library has some file system support.
I am assuming you are running on Linux or some POSIX system.
Your program just uses an external command (find(1)); if you want to read from such a command, you might use popen(3) with pclose, then you won't need a temporary file. BTW, you could use find . -type f -ls.
However, you don't need to use an external command, and it is safer (and faster) to avoid that.
Pedantically, a file name could contain a newline character, and with your approach you'll need to special case that. A file name could also contain a tab character (or other control characters) and in that case find . -type f behave specifically, and you would also need to special case. In practice, it is extremely poor taste and very unlikely to have a newline or tab character in a file name and you might forget these weird cases.
You could use nftw(3). You could recursively use opendir(3) & loop on readdir(3) (and later closedir).
Once you have a file path, you would use stat(2) to get that file's metadata, including its size (field st_size). BTW the /bin/ls and /usr/bin/find programs use that.
The readdir(3) function returns a struct dirent pointer ending with d_name; you probably want to skip the two entries for . and .. (so use strcmp(3) to compare with "." and "..", or do the compare the hard way). Then you'll build a complete file path using string catenation. You might use (in genuine C++) std::string or you could use snprintf(3) or asprintf(3) for that. If you readdir the current directory . you could call stat(2) directly on d_name field.
BTW exit(-1) is incorrect (and certainly poor taste). See exit(3). A much more readable alternative is exit(EXIT_FAILURE)
So I'm trying to write a little C++ program to check whether or not a directory exists on a Windows platform (I am aware that other languages are more suited for this type of use, but I want to do it in c++).
This is what I have so far (it compiles):
std::string DirectorySelector::SpecifyDirectory(void)
{
std::cout << "Enter directory for file renaming: ";
std::cin >> directory;
if (ValidateDirectory(directory) == 1) { SpecifyDirectory(); }
else { return directory; }
}
int DirectorySelector::ValidateDirectory(std::string directory)
{
error = "Error 01: Directory not found.";
std::ifstream fin (directory);
if (!fin)
{
std::cerr << error << "\n\n";
fin.close();
return 1;
}
else
{
fin.close();
return 2;
}
}
So obviously I'm currently asking for the user to input their desired directory as a string, not sure if this is a wise choice?
I have done a little research into whether Windows folders (directories) have an extension, but it appears not.
I assume I am missing something obvious, such as a predefined C++ keyword to use somewhere?
If any answers could be fully explained as to what is going on that would be fantastic, as I don't like to use stuff which I don't understand.
Plus any tips to do with coding standards that I may not be adhering to would obviously be greatly appreciated.
Thanks in advance.
If you want to use DIRENT (unix method) in windows then see here, advantage is cross platform (dirent is pretty much everywhere except windows):
http://www.softagalleria.net/dirent.php
If you want to use the Windows API for this:
How to check if directory exist using C++ and winAPI
For a portable (across many platforms) file-management system you could use boost::filesystem
The documentation may look a bit complex for a relative beginner but they probably give you examples that will enable you to get going on what you want, and if you get stuck you can always come back here and ask specifics.
Your existing code is incorrect in its use of ifstream which is used to open a file for read-only. You cannot use this to open a directory (to list its contents or see if it exists).
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())
This is a very simple question but wherever I look I get a different answer (is this because it's changed or will change in c++0x?):
In c++ how do I read two numbers from a text file and output them in another text file?
Additionally, where do I put the input file? Just in the project directory? And do I need to already have the output file? Or will one be created?
You're probably getting different answers because there are many different ways to do this.
Reading and writing two numbers can be pretty simple:
std::ifstream infile("input_file.txt");
std::ofstream outfile("output_file.txt");
int a, b;
infile >> a >> b;
outfile << a << "\t" << b;
You (obviously) need to replace "input_file.txt" with the name of a real text file. You can specify that file with an absolute or relative path, if you want. If you only specify the file name, not a path, that means it'll look for the file in the "current directory" (which may or may not be the same as the directory containing the executable).
When you open a file just for writing as I have above, by default any existing data will be erased, and replaced with what you write. If no file by that name (and again, you can specify the path to the file) exists, a new one will be created. You can also specify append mode, which adds new data to the end of the existing file, or (for an std::fstream) update mode, where you can read existing data and write new data.
If your program is a filter, i.e. it reads stuff from somewhere, and outputs stuff elsewhere, you will benefit of using standard input and standard output instead of named files. It will allow you to easily use the shell redirections to use files, saving your program to handle all the file operations.
#include <iostream>
int main()
{
int a, b;
std::cin >> a >> b;
std::cout << a << " " << b;
}
Then use it from the shell.
> cat my_input_file | my_program > my_output_file
Put in the same folder as the executable. Or you can use a file path to point at it.
It can be created if it does not exist.
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"