C++ get directory prefix - c++

For example I have the string "root/data/home/file1.txt" I would like to get "root/data/home" Is there a convenient function in C++ that allows me to do this or should I code it myself?

You can do basic string manipulation, i.e.
std::string path = "root/data/home/file1.txt";
// no error checking here
std::string prefix = path.substr(0, path.find_last_of('/'));
or take a third option like Boost.Filesystem:
namespace fs = boost::filesystem;
fs::path path = "root/data/home/file1.txt";
fs::path prefix = path.parent_path();

If you're on a POSIX system, try dirname(3).

There's certainly no convenient function in the language itself. The string library provides find_last_of, which should do well.

This is rather platform-dependent. For example, Windows uses '\' for a path separator (mostly), Unix uses '/', and MacOS (prior to OSX) uses ':'.
The Windows-specific API is PathRemoveFileSpec.

Related

Using std::filesystem::path to convert between separator types

I'm working on code that involves loading a file from a path which is constructed as a concatenation of a given "base" path and a secondary relative path, loaded from another file. For example (and where I'm running into an issue), the base path is "assets/models/", and the secondary path is "maps\map.png".
Straight concatenation of these two strings gives "assets/models/maps\map.png". When running on POSIX systems this fails to load. Up to now I've been sorting this out by just replacing the backslashes with forward slashes with
std::replace( path.begin(), path.end(), '\\', '/' );
but I'd like to use C++17's std::filesystem::path to do this instead.
The description of std::filesystem::path::make_preferred() suggests that it should replace the separators:
"Converts all directory separators in the generic-format view of the path to the preferred directory separator.
For example, on Windows, where \ is the preferred separator, the path foo/bar will be converted to foo\bar"
When implemented in the code, however, it doesn't convert anything. I've also verified that std::filesystem::path::preferred_separator is as expected - '/'.
Am I misunderstanding the purpose of make_preferred()? Or am I just using it wrong?
Here's a cut down version of the code that doesn't work (this isn't the implemented code, but is close enough to it):
const char * loadedPath = "maps\\map.png"
std::string loadedPathStr = std::string( loadedPath );
auto wPath = std::filesystem::path( loadedPathStr );
wPath = wPath.make_preferred();
basePath = std::filesystem::path( "./a/b/" );
auto totalPath = basePath / wPath;
auto wStr = totalPath.generic_string();
std::cout << wStr << std::endl;
This outputs "./a/b/maps\\map.png"
When debugging the implemented code, it looks like wPath is optimised out; there's no way of inspecting it.
Strangely, when I compile and run this standalone test program, it works as expected:
int main(){
assert( std::filesystem::path::preferred_separator == '/' );
const char * cPath = "maps\\map.png";
std::string path = std::string( cPath );
auto wPath = std::filesystem::path( path );
wPath = wPath.make_preferred();
std::string wStr = wPath.generic_string();
std::cout << wStr << std::endl;
}
This outputs "maps/map.png". I can't read. This also outputs the incorrect value.
Anyone know whats going on here?
EDIT:
Tried compiling with clang (using gcc before), and it works as expected (separator is converted). Ignore this, made a mistake in recompiling.
I'm running this on Linux, and the path exists.
Am I misunderstanding the purpose of make_preferred()?
Not entirely, but subtly yes. A directory separator (in the generic format) is either the preferred separator, or the fallback separator: /. On systems where the preferred separator is / (e.g. POSIX), directory separator is only /. On such systems make_preferred doesn't modify the path, which explains why it would be optimised out completely.
The simplest way to replace back slashes with forward slashes is std::replace. However, do note that back slashes are valid characters in file names in POSIX, so such conversion may break the use of file names that use it.
If you want to write cross-platform code with filesystem, you should try to stick with the generic format. The behavior of all other formats of filesystem strings is implementation-dependent.
An implementation that allows alternative directory separators will treat them as directory separators. But other, perfectly valid, implementations that don't recognize those separators will not recognize them. "/" is always a directory separator; whether "\" is a separator or not depends on the implementation.
make_preferred converts from the implementation's path format into the generic format. As such, it's behavior is implementation-dependent.
The main reason behind dealing in non-generic formats is when you're getting path strings from the native OS API. Such path strings will likely be in the implementation's format, so path needs to be able to recognize them and work with them. For string literals built into your program, you should always prefer the generic format (unless your application is OS-specific, or you are selecting different strings based on the OS your code is being used on).

Method for abstracting filesystems in a C program

I'm starting out a program in SDL which obviously needs to load resources for the filesystem.
I'd like file calls within the program to be platform-independent. My initial idea is to define a macro (lets call it PTH for path) that is defined in the preprocessor based on system type and and then make file calls in the program using it.
For example
SDL_LoadBMP(PTH("data","images","filename"));
would simply translate to something filesystem-relevant.
If macros are the accepted way of doing this, what would such macros look like (how can I check for which system is in use, concatenate strings in the macro?)
If not, what is the accepted way of doing this?
The Boost Filesystem module is probably your best bet. It has override for the "/" operator on paths so you can do stuff like...
ifstream file2( arg_path / "foo" / "bar" );
GLib has a number of portable path-manipulation functions. If you prefer C++, there's also boost::filesystem.
There's no need to have this as a macro.
One common approach is to abstract paths to use the forward slash as a separator, since that (almost accidentally!) maps very well to a large proportion of actual platforms. For those where it doesn't, you simply translate inside your file system implementation layer.
Looking at the python implementation for OS9 os.path.join (macpath)
def join(s, *p):
path = s
for t in p:
if (not s) or isabs(t):
path = t
continue
if t[:1] == ':':
t = t[1:]
if ':' not in path:
path = ':' + path
if path[-1:] != ':':
path = path + ':'
path = path + t
return path
I'm not familiar with developing under SDL on older Macs. Another alternative in game resources is to use a package file format, and load the resources into memory directly (such as a map < string, SDL_Surface > )
Thereby you would load one file (perhaps even a zip, unzipped at load time)
I would simply do the platform-equivalent version of chdir(data_base_dir); in your program's startup code, then use relative unix-style paths of the form "images/filename". The last systems where this would not work were MacOS 9, which is completely irrelevant now.

replacement for findfirst() and findnext()

Is there any replacement for findfirst() and findnext().
I am using microsoft visual c++ 2010 express and it doesn't support these functions neither the header file <dir.h> ?
I was looking to count the number of files in the directory using these functions but i am having a problem without these functions.
If there is no replacement of the mentioned functions is there any other way out. ? Some other functions ?
As 'iammilind' said in the comments (probably worthy of an answer) - you can use the windows api's FindFirstFile and FindNextFile functions, you just have to fill up a struct and iterate through the latter until you reach an invalid handle. These functions do work on console, but you must include the 'Windows.h' header.
These functions do come with a couple of pitfalls however, and if you want your code to run on anything other than windows you're probably better off using another header/library (such as Boost::Filesystem, mentioned by vBx).
Also, this may be of help:
C++ - Load all filename + count the number of files in a current directory + filter file extension
You can use Boost.Filesystem for that
In Windows you can use: _findnext, _findnext64, _findnexti64, _wfindnext, _wfindnext64, _wfindnexti64
If you use MinGW Developer Studio, this might help:
Assuming that you have the files in the dir you want to look will be:
sample1.txt
sample2.txt
sample3.txt
The code for the two files matching the pattern "s*" will be:
#include<stdio.h>
#include<io.h>
int main()
{
// the input pattern and output struct
char *pattern = "s*";
struct _finddata_t fileinfo;
// first file (sample1.txt)
int x = _findfirst(pattern, &fileinfo);
printf("%s" ,fileinfo.name);
// next file (sample2.txt)
_findnext(x, &fileinfo);
printf("%s" ,fileinfo.name);
}

Detect whether path is absolute or relative

Using C++, I need to detect whether given path (file name) is absolute or relative. I can use Windows API, but don't want to use third-party libraries like Boost, since I need this solution in small Windows application without expernal dependencies.
The Windows API has PathIsRelative. It is defined as:
BOOL PathIsRelative(
_In_ LPCTSTR lpszPath
);
Beginning with C++14/C++17 you can use is_absolute() and is_relative() from the filesystem library
#include <filesystem> // C++17 (or Microsoft-specific implementation in C++14)
std::string winPathString = "C:/tmp";
std::filesystem::path path(winPathString); // Construct the path from a string.
if (path.is_absolute()) {
// Arriving here if winPathString = "C:/tmp".
}
if (path.is_relative()) {
// Arriving here if winPathString = "".
// Arriving here if winPathString = "tmp".
// Arriving here in windows if winPathString = "/tmp". (see quote below)
}
The path "/" is absolute on a POSIX OS, but is relative on
Windows.
In C++14 use std::experimental::filesystem
#include <experimental/filesystem> // C++14
std::experimental::filesystem::path path(winPathString); // Construct the path from a string.

How to get directory of a file which is called from command line argument?

For example, I am calling my executable in ubuntu:
./foo temp/game1.txt temp/game2 txt
I am using realpath() to find the path to the game1.txt.
Using it however will give me the full path including the game1.txt name.
For example, it will come out as
/home/*/Download/temp/game1.txt
I want to erase game1.txt on that string so that I can use the string to use it to output other files in that folder.
Two questions.
Is realpath() alright to use for this kind of operation? Is there better way?
Can someone give me a quick way to erase "game1.txt" so that the string will be "/home/*/Download/temp/" save in a string format(not char)?
Thank you very much.
Don't really know Linux, but a general way for your second question:
#include <string>
#include <iostream>
int main(){
std::string fullpath("/home/*/Download/temp/game1.txt");
size_t last = fullpath.find_last_of('/');
std::string path = fullpath.substr(0,last+1);
std::cout << path;
}
See on Ideone.
You can use the dirname function for this: http://linux.die.net/man/3/dirname
Note that dirname will erase the trailing slash (except for the root directory); you'll have to append the slash back in when you append the filename.
Also, you don't actually need to use realpath for this purpose, you could simply use the argument as passed in, dirname will give you "temp" and you can append filenames to that.
man -S3 dirname
should do what you want
The cross-platform solution is
QFileInfo target_file_name(argv[1]);
QString absolute_path = target_file_name.absolutePath()
// e.g. /home/username/
QString some_other_file = QString("%1/another_file.txt").arg(absolute_path)
// => /home/username/another_file.txt
Boost.Filesystem can also do this easily. I just find the documentation of QFileInfo easily to navigate.
http://doc.qt.nokia.com/4.6/qfileinfo.html#absolutePath