30

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

Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
nidhal
  • 1,609
  • 4
  • 16
  • 15
  • 3
    Pet hate: is that a narrow STL string? You should be using Unicode strings for all file handling on Windows. – Rup Dec 15 '11 at 10:43
  • @Rup: really? Does the encoding matter when looking for `'/'` and `'\'`? As long as you are not interpreting the strings as ASCII/Latin1/whatever wrong assumption – sehe Dec 15 '11 at 10:50
  • @sehe No, sure, if the string is UTF-8 then you won't have problems with internationalisation etc. or someone feeding you a file with a Japanese name. But only UTF-8, and there's no point using UTF-8 when everything else uses UTF-16 - probably including wherever you got the string from in the first place. – Rup Dec 15 '11 at 11:08
  • @Rup: so know we know that _you prefer_ UTF16. For your info, Windows treats filenames as _opaque_ arrays of UTF-16 characters. Note also that UTF-16 is _still_ a variable-length character encoding; it doesn't actually buy you much over UTF-8. I would have understood if you argued UCS-2 (_fixed-length characters_) for simplicity of implementation, but then again, windows treats it as UCS-16 anyway – sehe Dec 15 '11 at 11:14
  • @nidhal: Better use slashes than backslashes: `"C:\MyDirectory\MyFile.bat"` contains two `\M` as escape characters. Better use `"C:/MyDirectory/MyFile.bat"` or if it has to be`"C:\\MyDirectory\\MyFile.bat"`. – Sebastian Mach Dec 15 '11 at 11:55
  • @sehe Well except when it's parsing the filename for path components obviously :-p My point is really 1) don't use a code page that can't represent all characters as you can easily get in trouble 2) input from the UI is passed as UTF-16, names from the filesystem APIs are passed as UTF-16, filenames you read from the registry etc. are passed as UTF-16, and all filesystem APIs accept UTF-16 characters: there's very rarely a good reason to make a round-trip to UTF-8. You're just making trouble for yourself, and if you define your API to accept UTF-8 filenames only you're making trouble for me. – Rup Dec 15 '11 at 12:04
  • @Rup: At least with UTF-8 [you can parse it for filename components just fine](http://en.wikipedia.org/wiki/UTF-8#Compared_to_other_multi-byte_encodings) (bullets 1 and 4) without interpreting byte sequences (possibly true for UTF-16 as well). Also, for general interest, see ['Should UTF-16 be considered harmful?'](http://programmers.stackexchange.com/questions/102205/should-utf-16-be-considered-harmful) – sehe Dec 15 '11 at 12:17
  • 1
    why reinvent the wheel, dear colleagues? Use libraries for that that have been tested and written by people who might know what they're doing. – Dmitry Ledentsov Jun 03 '14 at 05:54
  • Rejected attempted edit. Nubok, if you see this, **please do not correct *questions* with edits**, and **please do not delete important information from answers**. – Kyle Strand Mar 17 '15 at 23:57

13 Answers13

28

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);
}
hmjd
  • 120,187
  • 20
  • 207
  • 252
  • it's the answer only for the part, which is 'In this example, I should get...'. For a correct solution, a path handling library or an OS call should be used. – Dmitry Ledentsov Jun 03 '14 at 05:56
  • 5
    Fails if the filename (legitimately) uses *forward* slashes. Also fails to get the (absolute) directory if the filename is relative. Also relies on encoding being set correctly. I wouldn't flag this as "correct" when there's Offirmo's (portable) Boost solution. – DevSolar Jun 03 '14 at 05:57
26

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;
}
sehe
  • 374,641
  • 47
  • 450
  • 633
20

Use the Boost.filesystem parent_path() function.

Ex. argument c:/foo/bar => c:/foo

More examples here : path decomposition table and tutorial here.

Offirmo
  • 18,962
  • 12
  • 76
  • 97
15

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)

gerardw
  • 5,822
  • 46
  • 39
12

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('\\'));
Steztric
  • 2,832
  • 2
  • 24
  • 43
8

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;
}
jhasse
  • 2,379
  • 1
  • 30
  • 40
6

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.

Himanshu
  • 4,327
  • 16
  • 31
  • 39
5

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

Community
  • 1
  • 1
Jared B.
  • 59
  • 1
  • 5
5

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

Community
  • 1
  • 1
Ajit Vaze
  • 2,686
  • 2
  • 20
  • 24
  • Given the lack of a C#-like dirname function, this is absolutely the best method, as it doesn't depend on the format of the path. – Dana Aug 17 '17 at 14:25
1

This is proper winapi solution:

CString csFolder = _T("c:\temp\file.ext");
PathRemoveFileSpec(csFolder.GetBuffer(0));
csFolder.ReleaseBuffer(-1);
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();
}
TonySalimi
  • 8,257
  • 4
  • 33
  • 62
0

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);
Beetle
  • 1
  • 1
0

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("/");

Shadi Serhan
  • 309
  • 3
  • 9