c++ - Splitting an absolute file path - c++

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;
}

Related

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".

Recursive listing files in C++ doesn't enter all subdirectories

!!!Solved!!!
Thank you guys for your help, it's all working now. I made changes to my code as suggested by #RSahu and got it to work.
Thanks for all your input I've been really stuck with this.
To #Basile: I will definitely check that out but for this particular piece of code I'm not gonna use it because it looks way too complicated :) But thanks for suggestion.
Original question
I'm trying to make a C++ code to list all files in given directory and it's subdirectories.
Quick explanation
Idea is that function list_dirs(_dir, _files, _current_dir) will start in top directory and put files into vector _files and when it find a directory it will call itself on this directory. The _current_dir is there to be prepended to file name if in subdirectory because I need to know the path structure (it's supposed to generate sitemap.xml).
In list_dirs there is a call to list_dir which simply returns all files in current directory, not making difference between file and directory.
My problem
What codes does now is that it lists all files in original directory and then all files in one subdirectory but skipping all other subdirectories. It will list them but not the files in them.
And to be even more cryptic, it list files only in this one specific directory and none other. I tried running it in multiple locations but it never went into any other directory.
Thanks in advance and please note that I am beginner at C++ so don't be harsh ;)
LIST_DIR
int list_dir(const std::string& dir, std::vector<std::string>& files){
DIR *dp;
struct dirent *dirp;
unsigned fileCount = 0;
if ((dp = opendir(dir.c_str())) == NULL){
std::cout << "Error opening dir." << std::endl;
}
while ((dirp = readdir(dp)) != NULL){
files.push_back(std::string (dirp->d_name));
fileCount++;
}
closedir(dp);
return fileCount;
}
and LIST_DIRS
int list_dirs (const std::string& _dir, std::vector<std::string>& _files, std::string _current_dir){
std::vector<std::string> __files_or_dirs;
list_dir(_dir, __files_or_dirs);
std::vector<std::string>::iterator it = __files_or_dirs.begin();
struct stat sb;
while (it != __files_or_dirs.end()){
if (lstat((&*it)->c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){
/* how to do this better? */
if (*it == "." || *it == ".."){
__files_or_dirs.erase(it);
continue;
}
/* here it should go into sub-directory */
list_dirs(_dir + *it, _files, _current_dir + *it);
__files_or_dirs.erase(it);
} else {
if (_current_dir.empty()){
_files.push_back(*it);
} else {
_files.push_back(_current_dir + "/" + *it);
}
++it;
}
}
}
The main problem is in the line:
if (lstat((&*it)->c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){
You are using the name of a directory entry in the call to lstat. When the function is dealing with a sub-directory, the entry name does not represent a valid path. You need to use something like:
std::string entry = *it;
std::string full_path = _dir + "/" + entry;
if (lstat(full_path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){
Suggestions for improvement
Update list_dir so that it doesn't include "." or ".." in the output. It makes sense to me to exclude those files to start with.
int list_dir(const std::string& dir, std::vector<std::string>& files){
DIR *dp;
struct dirent *dirp;
unsigned fileCount = 0;
if ((dp = opendir(dir.c_str())) == NULL){
std::cout << "Error opening dir." << std::endl;
}
while ((dirp = readdir(dp)) != NULL){
std::string entry = dirp->d_name;
if ( entry == "." or entry == ".." )
{
continue;
}
files.push_back(entry);
fileCount++;
}
closedir(dp);
return fileCount;
}
In list_dirs, there is no need to erase items from _files_or_dirs. The code can be simplified with a for loop and by removing the calls to erase items from _files_or_dirs.
It's not clear to me what the purpose of _current_dir is. Perhaps it can be removed.
Here's an updated version of the function. _current_dir is used only to construct the value of the argument in the recursive call.
int list_dirs (const std::string& _dir,
std::vector<std::string>& _files, std::string _current_dir){
std::vector<std::string> __files_or_dirs;
list_dir(_dir, __files_or_dirs);
std::vector<std::string>::iterator it = __files_or_dirs.begin();
struct stat sb;
for (; it != __files_or_dirs.end() ; ++it){
std::string entry = *it;
std::string full_path = _dir + "/" + entry;
if (lstat(full_path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){
/* how to do this better? */
/* here it should go into sub-directory */
list_dirs(full_path, _files, _current_dir + "/" + entry);
} else {
_files.push_back(full_path);
}
}
}
For this line:
if (lstat((&*it)->c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){
Note that readdir and consequently list_dir only return the file name, not the full file path. So at this point (&*it)->c_str() only has a file name (e.g. "input.txt"), not the full path, so when you call lstat on a file in a subdirectory, the system can't find it.
To fix this, you will need to add in the file path before calling lstat. Something like:
string fullFileName;
if (dir.empty()){
fullFileName = *it;
} else {
fullFileName = dir + "/" + *it;
}
if (lstat(fullFileName.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)){
You may have to use _currentDir instead of dir, depending on what they are actually for (I couldn't follow your explanation).
I am not sure all of the problems in your code but I can tell you that this line and the other one similar to it are going to cause you problems:
__files_or_dirs.erase(it);
When you call erase you invalidate the iterator and references at or after the point of the erase, including the end() iterator (see this erase reference). You are calling erase and then not storing the returned iterator and are then looking at it again after this call which is not a good thing to do. You should at least change the line to this so that you capture the returned iterator which should point to the element just after the erased element (or end() if it was the last element)
it = __files_or_dirs.erase(it);
It also appears from the code you posted that you have a redundancy between _dir and _current_dir. You do not modify either of them. You pass them in as the same value and they stay the same value throughout the function execution. Unless this is simplified code and you are doing something else, I would recommend you remove the _current_dir one and just stick with _dir. You can replace the line in the while loop with _dir where you are building the file name and you will have simplified your code which is always a good thing.
A simpler way on Linux is to use the nftw(3) function. It is scanning recursively the file tree, and you give it some handler function.

How to separate a executing path to file path and parameter?

There are such lines as
C:\Program Files\Realtek\Audio\HDA\RAVCpl64.exe -s
in the registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
Now I want to separate the absolute path and parameters from the line.
If the line is
C:\Space Dir\Dot.Dir\Sample Name.Dot.exe param path."C:\Space Dir\Dot.Dir\Sample Name.Dot.exe"
Which separator should I use to deal with this line? Is there any Windows API function to solve this problem?
The function you want is in the standard C library that you can use in Windows.
char theDrive[5],thePath[MAX_PATH],theFilename[MAX_PATH],theExtension[MAX_PATH];
_splitpath(theSCTDataFilename,theDrive,thePath,theFilename,theExtension);
You can also use a more general tokenizing function like this which takes any string, a char and a CStringArray..
void tokenizeString(CString theString, TCHAR theToken, CStringArray *theParameters)
{
CString temp = "";
int i = 0;
for(i = 0; i < theString.GetLength(); i++ )
{
if (theString.GetAt(i) != theToken)
{
temp += theString.GetAt(i);
}
else
{
theParameters->Add(temp);
temp = "";
}
if(i == theString.GetLength()-1)
theParameters->Add(temp);
}
}
CStringArray thePathParts;
tokenizeString("your\complex\path\of\strings\separated\by\slashes",'/',&thePathParts);
This will give you an array of CString (CStringArray object) that contains each section of the input string. You can use this function to parse the major chunks then the minor ones as long as you know the seperator charactor you want to split the string on.

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;
}

get directory from file path 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("/");