2

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

3
  • Place your rubber duck -- en.wikipedia.org/wiki/Rubber_duck_debugging -- next to your keyboard, and explain to your rubber duck, in plain English, your logical, step by step, algorithm that does this. Once your rubber duck agrees that your algorithm will work, simply take your explanation, and translate it into code. Commented Feb 20, 2016 at 22:54
  • 1
    I'm not quite sure what options I have to split strings this way.. I know that I can split it based on whitespace using stringstreams. But this is a different delimiter. I'd like to split it according to the last '/' Commented Feb 20, 2016 at 22:56
  • The std::string class has methods to split, and chop up the string, in any way that pleases you. If you want to extract parts of the string before or after position #n, std::string offers several methods to do so. Commented Feb 20, 2016 at 23:00

7 Answers 7

8

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"
}
6

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());
4

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.

3

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.

1

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

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;
} 
1
  • You need to improve your code snippet: Add the required standard library headers, for example. Also, using an int for what should be a size_t (return value of find_first_of) is poor practice. Commented Oct 3, 2021 at 15:35
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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.