get directory from file path c++ - c++

What is the simplest way to get the directory that a file is in? I'm using this to find the working directory.
string filename = "C:\MyDirectory\MyFile.bat"
In this example, I should get "C:\MyDirectory".

The initialisation is incorrect as you need to escape the backslashes:
string filename = "C:\\MyDirectory\\MyFile.bat";
To extract the directory if present:
string directory;
const size_t last_slash_idx = filename.rfind('\\');
if (std::string::npos != last_slash_idx)
{
directory = filename.substr(0, last_slash_idx);
}

The quick and dirty:
Note that you must also look for / because it is allowed alternative path separator on Windows
#include <string>
#include <iostream>
std::string dirnameOf(const std::string& fname)
{
size_t pos = fname.find_last_of("\\/");
return (std::string::npos == pos)
? ""
: fname.substr(0, pos);
}
int main(int argc, const char *argv[])
{
const std::string fname = "C:\\MyDirectory\\MyFile.bat";
std::cout << dirnameOf(fname) << std::endl;
}

Use the Boost.filesystem parent_path() function.
Ex. argument c:/foo/bar => c:/foo
More examples here : path decomposition table and tutorial here.

C++17 provides std::filesystem::path. It may be available in C++11 in ; link with -lstdc++fs. Note the function does not validate the path exists; use std::filesystem::status to determine type of file (which may be filetype::notfound)

The MFC way;
#include <afx.h>
CString GetContainingFolder(CString &file)
{
CFileFind fileFind;
fileFind.FindFile(file);
fileFind.FindNextFile();
return fileFind.GetRoot();
}
or, even simpler
CString path(L"C:\\my\\path\\document.txt");
path.Truncate(path.ReverseFind('\\'));

As Question is old but I would like to add an answer so that it will helpful for others.
in Visual c++ you can use CString or char array also
CString filename = _T("C:\\MyDirectory\\MyFile.bat");
PathRemoveFileSpec(filename);
OUTPUT:
C:\MyDirectory
Include Shlwapi.h in your header files.
MSDN LINK here you can check example.

A very simple cross-platform solution (as adapted from this example for string::find_last_of):
std::string GetDirectory (const std::string& path)
{
size_t found = path.find_last_of("/\\");
return(path.substr(0, found));
}
This works for both cases where the slashes can be either backward or forward pointing (or mixed), since it merely looks for the last occurrence of either in the string path.
However, my personal preference is using the Boost::Filesystem libraries to handle operations like this. An example:
std::string GetDirectory (const std::string& path)
{
boost::filesystem::path p(path);
return(p.parent_path().string());
}
Although, if getting the directory path from a string is the only functionality you need, then Boost might be a bit overkill (especially since Boost::Filesystem is one of the few Boost libraries that aren't header-only). However, AFIK, Boost::Filesystem had been approved to be included into the TR2 standard, but might not be fully available until the C++14 or C++17 standard (likely the latter, based on this answer), so depending on your compiler (and when you're reading this), you may not even need to compile these separately anymore since they might be included with your system already. For example, Visual Studio 2012 can already use some of the TR2 filesystem components (according to this post), though I haven't tried it since I'm still using Visual Studio 2010...

You can use the _spliltpath function available in stdlib.h header. Please refer to this link for the same.
http://msdn.microsoft.com/en-us/library/aa273364%28v=VS.60%29.aspx

Since C++17 you can use std::filesystem::parent_path:
#include <filesystem>
#include <iostream>
int main() {
std::string filename = "C:\\MyDirectory\\MyFile.bat";
std::string directory = std::filesystem::path(filename).parent_path().u8string();
std::cout << directory << std::endl;
}

This is proper winapi solution:
CString csFolder = _T("c:\temp\file.ext");
PathRemoveFileSpec(csFolder.GetBuffer(0));
csFolder.ReleaseBuffer(-1);

If you have access to Qt, you can also do it like this:
std::string getDirectory(const std::string & file_path)
{
return QFileInfo(QString(file_path)).absolutePath().toStdString();
}

The way of Beetle)
#include<tchar.h>
int GetDir(TCHAR *fullPath, TCHAR *dir) {
const int buffSize = 1024;
TCHAR buff[buffSize] = {0};
int buffCounter = 0;
int dirSymbolCounter = 0;
for (int i = 0; i < _tcslen(fullPath); i++) {
if (fullPath[i] != L'\\') {
if (buffCounter < buffSize) buff[buffCounter++] = fullPath[i];
else return -1;
} else {
for (int i2 = 0; i2 < buffCounter; i2++) {
dir[dirSymbolCounter++] = buff[i2];
buff[i2] = 0;
}
dir[dirSymbolCounter++] = fullPath[i];
buffCounter = 0;
}
}
return dirSymbolCounter;
}
Using :
TCHAR *path = L"C:\\Windows\\System32\\cmd.exe";
TCHAR dir[1024] = {0};
GetDir(path, dir);
wprintf(L"%s\n%s\n", path, dir);

You can simply search the last "\" and then cut the string:
string filePath = "C:\MyDirectory\MyFile.bat"
size_t slash = filePath.find_last_of("\");
string dirPath = (slash != std::string::npos) ? filePath.substr(0, slash) : filePath;
make sure in Linux to search "/" instead of "\":
size_t slash = filePath.find_last_of("/");

Related

Searching for files in a directory by name using Visual Studio C++

I'm trying to create a program where I can search for some files in a directory on my PC, using Visual Studio C++.
As I'm not very experienced with that, I found this code (below) in another answer but couldn't find any explanation to the code.
I'm having a hard time figuring it out and would strongly appreciate any help possible.
If there's another way of doing this I would be pleased to know how.
Thank you!
"
Now you can get file names. Just compare a file name.
while ((dirp = readdir(dp)) != NULL) {
std::string fname = dirp->d_name;
if(fname.find("abc") != std::string::npos)
files.push_back(fname);
}
Also you can use scandir function which can register filter function.
static int filter(const struct dirent* dir_ent)
{
if (!strcmp(dir_ent->d_name, ".") || !strcmp(dir_ent->d_name, ".."))
return 0;
std::string fname = dir_ent->d_name;
if (fname.find("abc") == std::string::npos) return 0;
return 1;
}
int main()
{
struct dirent **namelist;
std::vector<std::string> v;
std::vector<std::string>::iterator it;
n = scandir( dir_path , &namelist, *filter, alphasort );
for (int i=0; i<n; i++) {
std::string fname = namelist[i]->d_name;
v.push_back(fname);
free(namelist[i]);
}
free(namelist);
return 0;
}
"
A better way of doing this would probably be using the new std::filesystem library. directory_iterators allow you to go through the contents of a directory. Since they are just iterators, you can combine them with standard algorithms like std::find_if to search for a particular entry:
#include <filesystem>
#include <algorithm>
namespace fs = std::filesystem;
void search(const fs::path& directory, const fs::path& file_name)
{
auto d = fs::directory_iterator(directory);
auto found = std::find_if(d, end(d), [&file_name](const auto& dir_entry)
{
return dir_entry.path().filename() == file_name;
});
if (found != end(d))
{
// we have found what we were looking for
}
// ...
}
We first create a directory_iterator d for the directory in which we want to search. We then use std::find_if() to go through the contents of the directory and search for an entry that matches the filename we are looking for. std::find_if() expects a function object as last argument that is applied to every visited element and returns true if the element matches what we are looking for. std::find_if() returns the iterator to the first element for which this predicate function returns true, otherwise it returns the end iterator. Here, we use a lambda as predicate that returns true when the filename component of the path of the directory entry we're looking at matches the wanted filename. Afterwards, we compare the iterator returned by std::find_if() to the end iterator to see if we have found an entry or not. In case we did find an entry, *found will evaluate to a directory_entry representing the respective file system object.
Note that this will require a recent version of Visual Studio 2017. Don't forget to set the language standard to /std:c++17 or /std:c++latest in the project properties (C++/Language).
Both methods use the find function of a std::string:
fname.find("abc")
This looks for "abc" in the fname string. If it's found it returns the index it starts at, otherwise it retruns std::string::npos, so they both check for that substring.
You may want to see if you have an exact match, using == instead. It depends.
If an appropriate filename is found, it's pushed back into a vector.
Your main function has
std::vector<std::string>::iterator it;
which it doesn't use.
I suspect that came over with some copy/paste.
You can use a range based for loop to see what's in your vector:
for(const std::string & name : v)
{
std::cout << name << '\n';
}
The filter function also checks against "." and ".." since these have special meanings - current dir and up one dir.
At that point, th C API has returned a char *, so they use strcmp, rather than std::string methods.
Edit:
n = scandir( dir_path , &namelist, *filter, alphasort );
uses n which you haven't declared.
Try
int n = scandir( dir_path , &namelist, *filter, alphasort );
Also, that uses dir_path which needs declaring somewhere.
For a quick fix, try
const char * dir_path = "C:\\";
(or whatever path you want, watching out for escaping backslashes with an extra backslash.
You probably want to pass this in as an arg to main.

I want to create a text file in cpp using ofstream

I want to create a file qbc.txt. I know how to create it, but I want to create a program that, if a file already exists with the same name, it would rename it to qbc(1).txt.
In C++17, boost's filesystem library was standardized as std::filesystem
It comes with a convenient std::filesystem::exists function.
It accepts a std::filesystem::path object, but fortunately those can be constructed with a std::string, making our program trivially easy:
std::string prefix = "qbc";
std::string extension = ".txt";
std::filesystem::path filename{prefix + extension};
int i = 0;
while (std::filesystem::exists(filename)){
filename = prefix + "(" + std::to_string(++i) + ")" + extension;
}
// now filename is "qbc(1)" or "qbc(2)" etc.
Unfortunately no compiler has full support for it at the time of this writing!
Here is a simple solution. The file_exists() function came from #Raviprakash in his response. I've added how to change the filename and try again until success. I've done an approach similar to this before in Python.
If you know that your program is the only one that will create or remove these files, then you can cache the last created one and simply create the next one instead of looping over all of the created ones every time. But this kind of optimization would only make sense if you plan to make hundreds of thousands of files this way.
#include <fstream>
#include <string>
bool file_exists(const std::string &filename) {
std::ifstream in(filename);
return in.good();
}
std::ofstream& open_new(std::ofstream &out, std::string prefix,
std::string suffix)
{
std::string filename = prefix + suffix;
unsigned int index = 0;
while (file_exists(filename)) {
index++;
filename = prefix + "(" + std::to_string(index) + ")" + suffix;
}
out.rdbuf()->open(filename, std::ios_base::out);
return out;
}
int main() {
std::string prefix = "qbc";
std::string suffix = ".txt";
std::ofstream out;
open_new(out, prefix, suffix);
out << "hello world!\n";
return 0;
}
I know the program needs some improvements but the general idea is here:
#include <fstream>
#include <string>
using namespace std;
inline bool file_exists(const std::string& name)
{
ifstream f(name.c_str());
return f.good();
}
int main()
{
string filename, name;
name = "qbc";
filename = name;
int counter = 1;
while (file_exists(filename+".txt")) {
string str = to_string(counter);
filename = name+ "(" + str + ")";
counter++;
}
filename += ".txt";
ofstream out(filename.c_str());
return 0;
}
I don't think this can be entirely solved using just the standard libraries. You can certainly keep picking a new file name until you find one that's unused and then create the new file (as the other answers have shown).
But there's an inherent race condition in that approach. What if another process creates a file between the time your program decides the name is available and the time it actually creates the file? Imagine two copies of your program both trying to write out files.
What you need is an atomic way to check for the file's existence and also to create the file. The normal way to do that is to first just try to create the file and then see if you succeeded or not. Unfortunately, I don't think the standard C++ or C libraries give you enough tools to do that. (I'd be happy to be proven wrong about that.)
Operating systems often provide APIs for doing just that. For example, Windows has GetTempFileName, which just keeps trying to create a new file until it succeeds. The key is that, once it succeeds, it keeps the file open so that it knows no other process can steal the name that's been selected.
If you tell us which OS you're using, we might be able to provide a more detailed answer.

Extracting the immediate parent directory from a file path using boost

Assuming I have
String t = "c:/foo/foo1/foo2/foo3/file.txt"
I want to extract "foo3/file.txt".
How can I do this (using boost or std)?
Here is what I've been trying to so far:
boost::filesystem::path pathToObject(t);
Using pathToObject.filename() I can extract the file name of course. And I've played around with t.find_last_of("/") but I really need like t.find_second_to_last_of("/").
string::find_last_of has an optional argument which lets you specify how far into the string you are looking.
So you can define
size_t second_to_last = t.find_last_of("/", t.find_last_of("/")-1);
std::string file_with_parent = t.substr(second_to_last+1);
The second argument tells him to only search before the last /.
WARNING: This might differ from what you want if you have stuff like "C:/bli/bla//blubb.txt". In general, paths can be complex and confusing and trying to conquer them with string manipulation will only work for very well-behaved input, which one usually can't assume.
I therefore recommended using a proper tool for this job.* But since the question claimed that find_last_of wouldn't do the job I felt quite compelled to remind people that the standard facilities are not entirely as impotent as many seem to believe them to be.
*I suspect the boost path lib to be one but I have never worked with it.
It is rather odd to extract a path like that. Maybe you are looking for a relative path? boost filesystem has a tool for that. Be sure to give the documentation a good look over. But to answer your question:
namespace bfs= boost::filesystem;
int main( ) {
bfs::path path( "c:/foo/foo1/foo2/foo3/file.txt" );
bfs::path target( path );
target.remove_filename( );
target= target.filename( ) / path.filename( );
std::cout << target << std::endl;
}
I don't have a compiler handy to test it, but based on the example here, this code should basically work or point you in about the right direction. It could probably be simplified a little bit even from what I've written here.
path p1( "c:/foo/foo1/foo2/foo3/file.txt" );
path p2;
for (path::iterator it(p1.end()), int i = 0; it != p1.begin() && i < 2; --it, ++i) {
path temp = p2;
p2 = it;
p2 /= temp;
}
Here is the solution I ended up using:
std::string t = pathObject.parent_path().filename().string();
t.append("/");
t.append(pathObject.filename().string());
Using parent_path gave me just the path. Then I used the filename to extract just the directory. Then I appended the filename of the child directory.
Following method return immediate parent directory.
#include <string>
string getParentDirectory(string& filePath)
{
if (filePath.empty() == false)
{
size_t toPos = filePath.find_last_of('\\') - 1;
if (toPos != string::npos)
{
size_t fromPos = filePath.find_last_of('\\', toPos);
if (fromPos != string::npos)
{
return filePath.substr(fromPos + 1, toPos - fromPos);
}
}
}
return "";
}
int main()
{
string str = "D:\\Devs\\Test\\sprite.png";
string parentDir = getParentDirectory(str);
return 0;
}
It prints the value of parentDir is "Test".

c++ - Splitting an absolute file path

I'm writing a C++ program for a school assignment. At some point, the question requires me to change directories, which I know how to do. However, the user will provide the program with the absolute path of a file. What I'm trying to do is to change the directory to where that file is. For example, if I'm in a directory dir2, and the user want to go to the file
/home/dir1/dir2/dir3/dir4/file
I would like to do
int ret = chdir("home/dir1/dir2/dir3/dir4");
My question is how can I split the user-given string into
/home/dir1/dir2/dir3/dir4/
and
file
EDITI figured it out. I first converted the absolute pathname from a const char* to a string. Then I used the .find_last_of("/") string member to find the position of the last "/" in the string. Then I used the .substr() member to get the substring from 0 to that position returned by .find_last_of
Put your path into an std::string and then you can do something like the below.
std::string path = "/home/person/dir/file";
std::size_t botDirPos = path.find_last_of("/");
// get directory
std::string dir = path.substr(0, botDirPos);
// get file
std::string file = path.substr(botDirPos, path.length());
// change directory.
chdir(dir.c_str());
Every other answer to this question finds "/" (Unix) or "\" (Windows), and chops up the string manually; this is verbose and subject to user error. C++17 now has the std::filesystem package, which cleanly extracts directory and filename from a path in an OS friendly manner:
#include <filesystem>
void Test()
{
std::filesystem::path path("/home/dir1/dir2/dir3/dir4/file");
std::string dir = path.parent_path().string(); // "/home/dir1/dir2/dir3/dir4"
std::string file = path.filename().string(); // "file"
}
Simply get the last index of the "/" character in the file path, and snip the file with it's extension from the string.
1) Check that the directory listing has a "/". If not - throw an error.
2) Get the last index of the "/" in the string.
3) Return a sub string of the directory string, using the last index of function result (a number) as your starting index and the total length of the directory string.
Hope that helps.
you can use
std::string dir_str = "path/file";
auto pos = dir_str.rfind("/");
if (pos!= std::string::npos) {
chdir("newpath"+dir_str.substr(pos));
//...
} else {
//do something;
}
there may be issues such as character / in the file name. but assuming this is just a toy program designed for a simple test it should work.
if you are handling files somewhat seriously (like iterating through a directory recursively) I would recommend using something like boost::file_system.
You can use strtok function from <string.h> to split path components and by the way keep track of each dir in the hierarchy.
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] ="/path/to/file";
char * pch;
char * temp;
pch = strtok (str,"/");
while ( (temp = strtok (NULL, "/") ) != NULL)
{
pch = temp;
}
printf("The file is: %s", pch);
return 0;
}
To add to plethora of answers, I devised this after looking up stat struct and function:
struct ab_path{
int delimiter = 0;
int extension = 0;
int length = 0;
char separator = '\0';
ab_path(){}
operator bool()
{ return (this->delimiter != 0) | (this->extension != 0) | (this->length != 0) | (this->separator != '\0') ;}
};
bool ab_path( const char* name , struct ab_path* ap ){
while(1){
if(name[ap->length] == '\0'){break;}
if(name[ap->length] == '.') {ap->extension = ap->length;}
if(name[ap->length] == '/')
{ap->delimiter = ap->length; ap->separator = name[ap->length];}
if(name[ap->length] == '\\')
{ap->delimiter = ap->length;ap->separator = name[ap->length];}
++ap->length;
}
return (bool)ap;
}
struct ab_path ap;
bool valid = ap_path("superb.naming.type", &ap );
But you can rewrite an ap->delimiter to accept container of some sort (std::vector,std::array... ) and store multiple delimiters.
This might help.
What it does, it splits the file path with corresponding directories/files and store the names in a vector.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
string filePath = "C:\\ProgramData\\Users\\CodeUncode\\Documents";
vector<string> directories;
size_t position=0, currentPosition=0;
while(currentPosition != -1)
{
currentPosition = filePath.find_first_of('\\', position);
directories.push_back(filePath.substr(position,currentPosition-position));
position = currentPosition+1;
}
for(vector<string>::iterator it = directories.begin(); it!=directories.end(); it++)
cout<<*it<<endl;
return 0;
}

Find only file name from full path of the file in vc++

Suppose there is a CString variable which store the full path of the file.Now I hava to find only file name from if.How to do it in vc++.
CString FileName = "c:\Users\Acer\Desktop\FolderName\abc.dll";
Now I want only abc.dll.
You can use PathFindFileName.
Remember that you have to escape the \ character in your path string!
Same as already stated above, but as u are using MFC framework, this would be the way to do it. Though this does not check files existence.
CString path= "c:\\Users\\Acer\\Desktop\\FolderName\\abc.dll";
CString fileName= path.Mid(path.ReverseFind('\\')+1);
std::string str = "c:\\Users\\Acer\\Desktop\\FolderName\\abc.dll";
std::string res = str.substr( str.find_last_of("\\") + 1 );
Will get you "abs.dll".
I would use Boost::FileSystem for filename manipulation as it understands what the parts of a name would be. The function you want here would be filename()
If you are just getting the filename you can do this using CString functions. First find the ast backslash using ReverseFind and then Right to get the string wanted.
The code below demonstrate extracting a file name from a full path
#include <iostream>
#include <cstdlib>
#include <string>
#include <algorithm>
std::string get_file_name_from_full_path(const std::string& file_path)
{
std::string file_name;
std::string::const_reverse_iterator it = std::find(file_path.rbegin(), file_path.rend(), '\\');
if (it != file_path.rend())
{
file_name.assign(file_path.rbegin(), it);
std::reverse(file_name.begin(), file_name.end());
return file_name;
}
else
return file_name;
}
int main()
{
std::string file_path = "c:\\Users\\Acer\\Desktop\\FolderName\\abc.dll";
std::cout << get_file_name_from_full_path(file_path) << std::endl;
return EXIT_SUCCESS;
}