how to subtract one path from another? - c++

So... I have a base path and a new path.New path contains in it base path. I need to see what is different in new path. Like we had /home/ and new path is /home/apple/one and I need to get from it apple/one. note - when I would create some path from (homePath/diffPath) I need to get that /home/apple/one again. How to do such thing with Boost FileSystem?

Using stem() and parent_path() and walk backwards from the new path until we get back to base path, this works, but I am not sure if it is very safe.
Be cautious, as the path "/home" and "/home/" are treated as different paths. The below only works if base path is /home (without trailing slash) and new path is guaranteed to be below base path in the directory tree.
#include <iostream>
#include <boost/filesystem.hpp>
int main(void)
{
namespace fs = boost::filesystem;
fs::path basepath("/home");
fs::path newpath("/home/apple/one");
fs::path diffpath;
fs::path tmppath = newpath;
while(tmppath != basepath) {
diffpath = tmppath.stem() / diffpath;
tmppath = tmppath.parent_path();
}
std::cout << "basepath: " << basepath << std::endl;
std::cout << "newpath: " << newpath << std::endl;
std::cout << "diffpath: " << diffpath << std::endl;
std::cout << "basepath/diffpath: " << basepath/diffpath << std::endl;
return 0;
}

Assuming you have:
namespace fs = std::filesystem; // or boost::filesystem
fs::path base = "/home/usera"
fs::path full = "/home/usera/documents/doc"
If you want to extract documents/doc, you can do that with lexically_relative:
fs::path diff = full.lexically_relative(base);
assert( diff == fs::path("documents/doc") );
This works for base = "/home/usera" or base = "home/usera/". If full does not contain base, this may give you a pretty long path with lots of .. instead of getting an error.
std::filesystem::path::lexically_relative requires C++17

Other solution, if you know that newpath really belongs to basepath, could be:
auto nit = newpath.begin();
for (auto bit = basepath.begin(); bit != basepath.end(); ++bit, ++nit)
;
fs::path = path(nit, newpath.end());

Related

How to best go to the parent directory of a directory with std::filesystem in c++ [duplicate]

As explained in the documentation, the expected output of the following is:
boost::filesystem::path filePath1 = "/home/user/";
cout << filePath1.parent_path() << endl; // outputs "/home/user"
boost::filesystem::path filePath2 = "/home/user";
cout << filePath2.parent_path() << endl; // outputs "/home"
The question is, how do you deal with this? That is, if I accept a path as an argument, I don't want the user to care whether or not it should have a trailing slash. It seems like the easiest thing to do would be to append a trailing slash, then call parent_path() TWICE to get the parent path of "/home" that I want:
boost::filesystem::path filePath1 = "/home/user/";
filePath1 /= "/";
cout << filePath1.parent_path().parent_path() << endl; // outputs "/home"
boost::filesystem::path filePath2 = "/home/user";
filePath2 /= "/";
cout << filePath2.parent_path().parent_path() << endl; // outputs "/home"
but that just seems ridiculous. Is there a better way to handle this within the framework?
You can use std::filesystem::canonical with C++17:
namespace fs = std::filesystem;
fs::path tmp = "c:\\temp\\";
tmp = fs::canonical(tmp); // will remove slash
fs::path dir_name = tmp.filename(); // will get temp
There is a (undocumented?) member function: path& path::remove_trailing_separator();
I tried this and it worked for me on Windows using boost 1.60.0:
boost::filesystem::path filePath1 = "/home/user/";
cout << filePath1.parent_path() << endl; // outputs "/home/user"
cout << filePath1.remove_trailing_separator().parent_path() << endl; // outputs "/home"
boost::filesystem::path filePath2 = "/home/user";
cout << filePath2.parent_path() << endl; // outputs "/home"
cout << filePath2.remove_trailing_separator().parent_path() << endl; // outputs "/home"
Seems like it, although I would recommend doing a previous manipulation with the directory string instead of calling twice to parent_path():
std::string directory = "/home/user"; // Try with "/home/user/" too, result is the same
while ((directory.back() == '/') || (directory.back() == '\\')))
directory.erase(directory.size()-1);
boost::filesystem::path filePath(directory);
std::cout << filePath.parent_path() << std::endl; // outputs "/home"
It is important to note that std::string::back() is a C++11 feature. Should you need to compile with a previous version you will have to change the algorithm a bit.
To remove the trailing separator from a path that is to a directory, so far this is working for me:
/**
* Creates lexically normal (removes extra path separators and dots) directory
* path without trailing path separator slash(es)
* #param dir_path - directory path to normalize
*/
void normalize_dir_path(boost::filesystem::path& dir_path) {
// #HACK - append non-existing file to path so that we may later resolve
// normalized directory path using parent_path()
dir_path /= "FILE.TXT";
// Remove unneeded dots and slashes
dir_path = dir_path.lexically_normal();
// Remove trailing slash from original path!
dir_path = dir_path.parent_path();
}
The above answer is similar to OP's original posted workaround (add '/') in combination with Wurmloch's comment about using lexically_normal(). One advantage is that only the documented methods from boost::filesystem are used. One possible disadvantage is that caller must be confident the input argument dir_path is intended to be a directory and not a regular file.
Using the normalize_dir_path(...) method to answer OP's question:
boost::filesystem::path filePath1 = "/home/user/";
normalize_dir_path(filePath1); // filePath1 is now "/home/user"
cout << filePath1.parent_path() << endl; // outputs "/home"
boost::filesystem::path filePath2 = "/home/user";
normalize_dir_path(filePath2); // filePath2 is now "/home/user"
cout << filePath2.parent_path() << endl; // outputs "/home"
boost::filesystem::path filePath3 = "/home/user/.";
normalize_dir_path(filePath3); // filePath3 is now "/home/user"
cout << filePath3.parent_path() << endl; // outputs "/home"
Update
Just realized that boost::filesystem::path::lexically_normal() is only available on BOOST version >= 1_60_0. For earlier versions, there appears to be a deprecated function available by default boost::filesystem::path::normalize() (as long as BOOST_FILESYSTEM_NO_DEPRECATED is not defined). So, my current normalize directory path method is along the lines:
#include <boost/version.hpp>
void normalize_dir_path(boost::filesystem::path& dir_path) {
// #HACK - append non-existing file to path so that we may later resolve
// normalized directory path using parent_path()
dir_path /= "FILE.TXT";
// Remove unneeded dots and slashes
#if BOOST_VERSION >= 106000
dir_path = dir_path.lexically_normal();
#else
dir_path.normalize();
#endif
// Remove trailing slash from original path!
dir_path = dir_path.parent_path();
}

How to create directory c++ (using _mkdir)

Today I did a lot of research online about how to create a directory on C++
and found a lot of way to do that, some easier than others.
I tried the _mkdir function using _mkdir("C:/Users/..."); to create a folder. Note that the argument of function will be converted into a const char*.
So far, so good, but when I want to change the path, it does not work (see the code below). I have a default string path "E:/test/new", and I want to create 10 sub-folders: new1, new2, newN, ..., new10.
To do that, I concatenate the string with a number (the counter of the for-loop), converted into char using static_cast, then I transform the string using c_str(), and assign it to a const char* variable.
The compiler has no problem compiling it, but it doesn't work. It prints 10 times "Impossible create folder n". What's wrong?
I probably made a mistake when transforming the string using c_str() to a get a const char*?.
Also, is there a way to create a folder using something else? I looked at CreateDirectory(); (API) but it uses keyword like DWORD HANDLE, etc., that are a little bit difficult to understand for a no-advanced level (I don't know what these mean).
#include <iostream>
#include <Windows.h>
#include<direct.h>
using namespace std;
int main()
{
int stat;
string path_s = "E:/test/new";
for (int i = 1; i <= 10; i++)
{
const char* path_c = (path_s + static_cast<char>(i + '0')).c_str();
stat = _mkdir(path_c);
if (!stat)
cout << "Folder created " << i << endl;
else
cout << "Impossible create folder " << i << endl;
Sleep(10);
}
return 0;
}
If your compiler supports c++17, you can use filesystem library to do what you want.
#include <filesystem>
#include <string>
#include <iostream>
namespace fs = std::filesystem;
int main(){
const std::string path = "E:/test/new";
for(int i = 1; i <= 10; ++i){
try{
if(fs::create_directory(path + std::to_string(i)))
std::cout << "Created a directory\n";
else
std::cerr << "Failed to create a directory\n";\
}catch(const std::exception& e){
std::cerr << e.what() << '\n';
}
}
return 0;
}
The problem is that (path_s + static_cast<char>(i + '0')) creates a temporary object. One whose life-time ends (and is destructed) just after c_str() has been called.
That leaves you with a pointer to a string that no longer exist, and using it in almost any way will lead to undefined behavior.
Instead save the std::string object, and call c_str() just when needed:
std::string path = path_s + std::to_string(i);
_mkdir(path.c_str());
Note that under Linux, you can use the mkdir command as follows:
#include <sys/stat.h>
...
const int dir_err = mkdir("foo", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (-1 == dir_err){
printf("Error creating directory!n");
exit(1);
}
More information on it can be gleaned from reading man 2 mkdir.

howto parse a path to a vector using c++

I need to parse the path part of a URL for a "router" as part of a REST web service. I'm using the PION library for handling HTTP requests. This library does not seem to have any functionality for retrieving parts of the URL path - or so it seems. I cannot find another library that does this. http://www.w3.org/Library/src/HTParse.c does not give parts of the path for example.
Is there a quicker, more robust way of doing this:
std::vector<std::string> parsePath(std::string path)
{
std::string delimiter = "/";
std::string part = "";
std::size_t firstPos = 0;
std::size_t secondPos = 0;
std::vector<std::string> parts;
while (firstPos != std::string::npos)
{
firstPos = path.find(delimiter, firstPos);
secondPos = path.find(delimiter, firstPos + 1);
part = path.substr(firstPos + 1, (secondPos - 1) - firstPos);
if (part != "") parts.push_back(part);
firstPos = secondPos;
}
return parts;
}
If you have the freedom to use Boost, the easiest way to parse filesystem paths would be to use the filesystem library, which has the advantage of being platform-independent and handling both POSIX and Windows path variants:
boost::filesystem::path p1("/usr/local/bin");
boost::filesystem::path p2("c:\\");
std::cout << p1.filename() << std::endl; // prints "bin"
std::cout << p1.parent_path() << std::endl; // prints "/usr/local"
To iterate through each element of the path, you can use a path iterator:
for (auto const& element : p1)
std::cout << element << std::endl;
prints
"/"
"usr"
"local"
"bin"
Without Boost, choose one of the many ways to parse a delimited string.

How can I extract the file name and extension from a path in C++

I have a list of files stored in a .log in this syntax:
c:\foto\foto2003\shadow.gif
D:\etc\mom.jpg
I want to extract the name and the extension from this files. Can you give a example of a simple way to do this?
To extract a filename without extension, use boost::filesystem::path::stem instead of ugly std::string::find_last_of(".")
boost::filesystem::path p("c:/dir/dir/file.ext");
std::cout << "filename and extension : " << p.filename() << std::endl; // file.ext
std::cout << "filename only : " << p.stem() << std::endl; // file
For C++17:
#include <filesystem>
std::filesystem::path p("c:/dir/dir/file.ext");
std::cout << "filename and extension: " << p.filename() << std::endl; // "file.ext"
std::cout << "filename only: " << p.stem() << std::endl; // "file"
Reference about filesystem: http://en.cppreference.com/w/cpp/filesystem
std::filesystem::path::filename
std::filesystem::path::stem
As suggested by #RoiDanto, for the output formatting, std::out may surround the output with quotations, e.g.:
filename and extension: "file.ext"
You can convert std::filesystem::path to std::string by p.filename().string() if that's what you need, e.g.:
filename and extension: file.ext
If you want a safe way (i.e. portable between platforms and not putting assumptions on the path), I'd recommend to use boost::filesystem.
It would look somehow like this:
boost::filesystem::path my_path( filename );
Then you can extract various data from this path. Here's the documentation of path object.
BTW: Also remember that in order to use path like
c:\foto\foto2003\shadow.gif
you need to escape the \ in a string literal:
const char* filename = "c:\\foto\\foto2003\\shadow.gif";
Or use / instead:
const char* filename = "c:/foto/foto2003/shadow.gif";
This only applies to specifying literal strings in "" quotes, the problem doesn't exist when you load paths from a file.
You'll have to read your filenames from the file in std::string. You can use the string extraction operator of std::ostream. Once you have your filename in a std::string, you can use the std::string::find_last_of method to find the last separator.
Something like this:
std::ifstream input("file.log");
while (input)
{
std::string path;
input >> path;
size_t sep = path.find_last_of("\\/");
if (sep != std::string::npos)
path = path.substr(sep + 1, path.size() - sep - 1);
size_t dot = path.find_last_of(".");
if (dot != std::string::npos)
{
std::string name = path.substr(0, dot);
std::string ext = path.substr(dot, path.size() - dot);
}
else
{
std::string name = path;
std::string ext = "";
}
}
Not the code, but here is the idea:
Read a std::string from the input stream (std::ifstream), each instance read will be the full path
Do a find_last_of on the string for the \
Extract a substring from this position to the end, this will now give you the file name
Do a find_last_of for ., and a substring either side will give you name + extension.
The following trick to extract the file name from a file path with no extension in c++ (no external libraries required):
#include <iostream>
#include <string>
using std::string;
string getFileName(const string& s) {
char sep = '/';
#ifdef _WIN32
sep = '\\';
#endif
size_t i = s.rfind(sep, s.length());
if (i != string::npos)
{
string filename = s.substr(i+1, s.length() - i);
size_t lastindex = filename.find_last_of(".");
string rawname = filename.substr(0, lastindex);
return(rawname);
}
return("");
}
int main(int argc, char** argv) {
string path = "/home/aymen/hello_world.cpp";
string ss = getFileName(path);
std::cout << "The file name is \"" << ss << "\"\n";
}
I also use this snippet to determine the appropriate slash character:
boost::filesystem::path slash("/");
boost::filesystem::path::string_type preferredSlash = slash.make_preferred().native();
and then replace the slashes with the preferred slash for the OS. Useful if one is constantly deploying between Linux/Windows.
For linux or unix machines, the os has two functions dealing with path and file names. use man 3 basename to get more information about these functions.
The advantage of using the system provided functionality is that you don't have to install boost or needing to write your own functions.
#include <libgen.h>
char *dirname(char *path);
char *basename(char *path);
Example code from the man page:
char *dirc, *basec, *bname, *dname;
char *path = "/etc/passwd";
dirc = strdup(path);
basec = strdup(path);
dname = dirname(dirc);
bname = basename(basec);
printf("dirname=%s, basename=%s\n", dname, bname);
Because of the non-const argument type of the basename() function, it is a little bit non-straight forward using this inside C++ code. Here is a simple example from my code base:
string getFileStem(const string& filePath) const {
char* buff = new char[filePath.size()+1];
strcpy(buff, filePath.c_str());
string tmp = string(basename(buff));
string::size_type i = tmp.rfind('.');
if (i != string::npos) {
tmp = tmp.substr(0,i);
}
delete[] buff;
return tmp;
}
The use of new/delete is not good style. I could have put it into a try/catch
block in case something happened between the two calls.
Nickolay Merkin's and Yuchen Zhong's answers are great, but however from the comments you can see that it is not fully accurate.
The implicit conversion to std::string when printing will wrap the file name in quotations. The comments aren't accurate either.
path::filename() and path::stem() returns a new path object and path::string() returns a reference to a string. Thus something like std::cout << file_path.filename().string() << "\n" might cause problems with dangling reference since the string that the reference points to might have been destroyed.

Getting a directory name from a filename

I have a filename (C:\folder\foo.txt) and I need to retrieve the folder name (C:\folder) in C++. In C# I would do something like this:
string folder = new FileInfo("C:\folder\foo.txt").DirectoryName;
Is there a function that can be used in C++ to extract the path from the filename?
Using Boost.Filesystem:
boost::filesystem::path p("C:\\folder\\foo.txt");
boost::filesystem::path dir = p.parent_path();
Example from http://www.cplusplus.com/reference/string/string/find_last_of/
// string::find_last_of
#include <iostream>
#include <string>
using namespace std;
void SplitFilename (const string& str)
{
size_t found;
cout << "Splitting: " << str << endl;
found=str.find_last_of("/\\");
cout << " folder: " << str.substr(0,found) << endl;
cout << " file: " << str.substr(found+1) << endl;
}
int main ()
{
string str1 ("/usr/bin/man");
string str2 ("c:\\windows\\winhelp.exe");
SplitFilename (str1);
SplitFilename (str2);
return 0;
}
In C++17 there exists a class std::filesystem::path using the method parent_path.
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
for(fs::path p : {"/var/tmp/example.txt", "/", "/var/tmp/."})
std::cout << "The parent path of " << p
<< " is " << p.parent_path() << '\n';
}
Possible output:
The parent path of "/var/tmp/example.txt" is "/var/tmp"
The parent path of "/" is ""
The parent path of "/var/tmp/." is "/var/tmp"
There is a standard Windows function for this, PathRemoveFileSpec. If you only support Windows 8 and later, it is highly recommended to use PathCchRemoveFileSpec instead. Among other improvements, it is no longer limited to MAX_PATH (260) characters.
Why does it have to be so complicated?
#include <windows.h>
int main(int argc, char** argv) // argv[0] = C:\dev\test.exe
{
char *p = strrchr(argv[0], '\\');
if(p) p[0] = 0;
printf(argv[0]); // argv[0] = C:\dev
}
auto p = boost::filesystem::path("test/folder/file.txt");
std::cout << p.parent_path() << '\n'; // test/folder
std::cout << p.parent_path().filename() << '\n'; // folder
std::cout << p.filename() << '\n'; // file.txt
You may need p.parent_path().filename() to get name of parent folder.
Use boost::filesystem. It will be incorporated into the next standard anyway so you may as well get used to it.
I'm so surprised no one has mentioned the standard way in Posix
Please use basename / dirname constructs.
man basename
_splitpath is a nice CRT solution.
Standard C++ won't do much for you in this regard, since path names are platform-specific. You can manually parse the string (as in glowcoder's answer), use operating system facilities (e.g. http://msdn.microsoft.com/en-us/library/aa364232(v=VS.85).aspx ), or probably the best approach, you can use a third-party filesystem library like boost::filesystem.
Just use this: ExtractFilePath(your_path_file_name)