43

I would like to write a function :

inline char separator()
{
    /* SOMETHING */
}

that returns the file separator of the system in standard C/C++/C++11 ? (I mean slash or backslash depending on the system). Is there a way to achieve this ?

Vincent
  • 57,703
  • 61
  • 205
  • 388
  • 1
    I know it says "standard C++" but if you don't mind using boost... http://stackoverflow.com/questions/8384477/obtain-platforms-path-separator-using-boost-filesystem – ta.speot.is Oct 19 '12 at 09:45
  • 2
    Boost is written in standard C++ so should be acceptable - see especially http://www.boost.org/doc/libs/1_51_0/libs/filesystem/doc/tutorial.html#Class-path-formats – mmmmmm Oct 19 '12 at 09:47
  • 4
    How much does it matter? The Windows APIs all accept both slash and backslash as the separator. `cmd.exe` is fussy; it uses slash to indicate options and therefore requires backslash in paths. For presentation to users, it is probably better to use backslashes, but with URLs using slashes, people probably aren't as hung up on it as they once were. – Jonathan Leffler Oct 19 '12 at 10:36
  • 5
    @Mark *"Boost is written in standard C++ so should be acceptable"* - Well, so is *Qt*, so just use `QDir::separator().toAscii()`. – Christian Rau Oct 19 '12 at 10:39
  • 1
    From [MSDN](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#canonicalizing-separators): All forward slashes (`/`) are converted into the standard Windows separator, the back slash (\\). If they are present, a series of slashes that follow the first two slashes are collapsed into a single slash. – mja Nov 04 '18 at 14:29
  • Options are not parsed by the OS in either DOS/Windows or UNIX. All parameters are passed in via argv[] and are parsed by application code. By convention (usually via common libraries), DOS/Windows uses "/" and UNIX uses "-", but there's nothing forcing this behavior. For example, DOS/Windows ports of UNIX utilities usually use "-". – David C. Nov 06 '18 at 15:58
  • 1
    Some functions in the winapi DO NOT convert slashes to backslashes! @mja See LoadLibraryW (I think it was this one) – Paul Stelian Aug 07 '20 at 15:13

7 Answers7

53

I'm not sure how to do it other than by checking ifdefs

inline char separator()
{
#ifdef _WIN32
    return '\\';
#else
    return '/';
#endif
}

or (as suggested by PaperBirdMaster)

const char kPathSeparator =
#ifdef _WIN32
                            '\\';
#else
                            '/';
#endif
simonc
  • 41,632
  • 12
  • 85
  • 103
  • 2
    All windows version define __WIN32 ?? – Jeyaram Oct 19 '12 at 09:45
  • I believe so - see [MSDN](http://msdn.microsoft.com/en-us/library/b0084kay.aspx) or a [previous SO answer](http://stackoverflow.com/a/6679822/311966). Btw, its _WIN32 (single leading _) – simonc Oct 19 '12 at 09:52
  • 29
    Just a note - Windows supports forward slash (/) as a path separator as well. – Eugene Mayevski 'Callback Oct 19 '12 at 09:56
  • 2
    Another note: why a inline function and not a `const char`? – PaperBirdMaster Oct 19 '12 at 10:04
  • 2
    I was just replying in the style suggested by the OP. You're correct that a `const char` would be just as good. I'll add that to the answer – simonc Oct 19 '12 at 10:05
  • @PaperBirdMaster - on early Macs the path separator was "::". – Pete Becker Oct 19 '12 at 15:08
  • I'm probably being pedantic here but what if they are on *nix using a non-standard filesystem? – tom Dec 21 '14 at 15:45
  • 1
    Mac OS uses ":" as a separator, with the first term (text before the first ":") representing the volume name. "::" represents the parent directory (like UNIX's "..") But that is only meant for some specialized cases (like scripting.) Apps should use file-dialogs for user-input and specialized OS-defined objects in-memory and for persistency. – David C. May 15 '15 at 13:56
  • @EugeneMayevski'AlliedBits: Does it really? Can you link to that? Also, if that's true, does that mean we can simply _always_ use forward slash to access files? – einpoklum Jan 26 '18 at 12:18
  • 2
    @einpoklum Good question. My comment was not exactly correct. Win32 API functions accept slash as the path separator and convert it to the backslash when calling NT API functions (except when dealing with paths that start with \\?\ ). The details can be found on https://msdn.microsoft.com/en-us/library/aa365247.aspx . – Eugene Mayevski 'Callback Jan 26 '18 at 12:24
  • 1
    This is wrong. It's not this simple when internationalization is involved. See [here](https://blogs.msdn.microsoft.com/oldnewthing/20051014-20/?p=33753). – user541686 Apr 22 '18 at 00:21
  • 1
    The links in the page mentioned by @user541686 don't work anymore. Mickael Kaplan's blog has been archived here http://archives.miloush.net/michkap/archive/2005/09/17/469941.html – user276648 Feb 19 '20 at 05:26
  • `/` and \ are not the only path separators. [Many other OSes use `.`, `:`, `>`... as separators](https://en.wikipedia.org/wiki/Path_(computing)#Representations_of_paths_by_operating_system_and_shell). And Windows is also not the only OS that use \ because Symbian, CP/M, DOS... also use that – phuclv Oct 25 '22 at 17:02
26

If your compiler offers c++17 capabilities, then you can use std::filesystem::path::preferred_separator which yields the preferred separator char depending on your platform. For example, on Windows this would usually be \ whereas on Linux you'd get /.

See this for more information.

Overblade
  • 656
  • 2
  • 7
  • 25
  • 2
    Minor nag, nothing major. The documentation about what `preferred_separator` should be is only an example; i.e. on Windows, you'd get a `\\ ` and on POSIX, you'd get a `/`. Other platforms may provide other results. – Clearer Jun 19 '18 at 11:51
  • This is no longer experimental: https://en.cppreference.com/w/cpp/filesystem/path – aardvarkk Jul 26 '21 at 05:04
  • For anyone using c++14, you can use std::experimental::filesystem::path::preferred_separator . – TheTomer Mar 30 '22 at 18:18
14

This question is really hinting at a much nastier problem.

If you simply care about UNIX vs. Winodws and you only care about directories and files, then what you've already seen will (mostly) work, but the more generic issue of splicing a path name into its components is a much uglier problem. Depending on the platform, a path may include one or more of:

  • Volume identifier
  • List of directories
  • File-name
  • Sub-stream within the file
  • Version number

While there are 3rd party libraries (like various CPAN Perl modules, Boost, and others) for this, and every OS includes system functions for this, there's nothing built-in to C for this and the C++ standard only gained this functionality (by incorporating the Boost module) in 2017.

Some examples of what such a function may need to deal with are:

  • UNIX and UNIX-like systems use a list of strings separated by "/" characters, with a leading "/" to indicate an absolute path (vs. a relative path). In some contexts (like NFS), there may also be a host-name prefix (with a ":" delimiter)
  • DOS and DOS-derived OS's (Windows, OS/2 and others) use "\" as a directory separator (with the APIs also accepting "/"), but paths may also be prefixed with volume information. It could be a drive letter ("C:"), or a UNC share name ("\\MYSERVER\SHARE\") There are additional prefixes to represent different kinds of servers and suffixes to represent non-default streams within a file.
  • Macs (Classic Mac OS, Carbon and some Cocoa APIs) use ":" as a directory separator, with the first term being a volume name, not a directory name. Mac files may also contain sub-streams ("forks"), which are accessed via the same name using special-purpose APIs. This is especially important for the resource fork, which is used extensively in classic Mac software.
  • Mac OS X, when using the UNIX APIs generally does what UNIX-like systems do, but they can also represent named sub-streams ("forks") by suffixing a "." followed by the fork-name to the file-name.
  • The latest versions of Cocoa (Mac OS X, iOS, etc.) recommend using a URL-based API to represent files, due to the ever-increasing complexity of this problem. Think about things like cloud-based documents and other complicated networked file systems.
  • VMS is pretty complicated (https://web.archive.org/web/20160324205714/http://www.djesys.com/vms/freevms/mentor/vms_path.html), but it has components that represent a volume, directory-path, file and file-revision.

There are many others as well.

It is worth noting that the C++17 filesystem library does not cover all of these possibilities. The std::filesystem::path consists of an optional root-name (a volume identifier), an optional root-directory (to identify absolute paths), and a sequence of filenames separated by directory separators. This covers everything likely to be valid on UNIX platforms and the majority of use-cases for other platforms, but is not comprehensive. For example, it does not have any support for sub-streams (relying on the OS to somehow map them onto a file name - which is done by Mac OS X, but not classic MacOS). It also does not include support for file version numbers.

See also Wikipedia's entry on Path and the C++17 std::filesystem::path class

http://en.cppreference.com/w/cpp/filesystem

I recommend you look at what you want to do with the directory separator (extract the base-name, break a path into a list of directories, etc.) and write a function to do that. If you're using C++17 (and you are certain your code won't be compiled by a pre-17 C++ compiler) then you can (probably) use standard C++ library code to write a portable implementation of this function. If not, that function will need to use platform-specific #ifdefs for each platform you will be supporting, using a #error if none of the conditions are met, to force you to to add conditions for unexpected platforms.

Or use a 3rd party library (like Boost) that includes functions for all of this, if that is acceptable.

David C.
  • 777
  • 8
  • 18
  • Please revise your answer in light of C++ 's getting a [standard filesystem library](http://en.cppreference.com/w/cpp/filesystem) in C++17. – einpoklum Jan 26 '18 at 12:20
13

that can be something like this

#if defined(WIN32) || defined(_WIN32) 
#define PATH_SEPARATOR "\\" 
#else 
#define PATH_SEPARATOR "/" 
#endif 
twid
  • 6,368
  • 4
  • 32
  • 50
  • 2
    note: like the accepted answer, you probably want to define the symbol as char literal instead (with single quotes) – ardnew Apr 20 '15 at 17:42
  • As long as you only care about UNIX vs. Microsoft (DOS, Windows, etc.) To be universal, the code will get much uglier (see my full answer.) – David C. May 15 '15 at 14:12
  • 2
    @ardnew, that depends. If you want to use it on string pointers in C, then there is no big difference if it's a character or a string, you just need to pick the right format specifier for `snprintf()`. On the other hand the string version is more convenient if you want to use it on literal strings. With the string version you'll be able to concatenate literals by just writing something like `"base_path" PATH_SEPARATOR "final_path"`. You won't be able to do that if you define the separator as a character. – Alexander Amelkin Jun 14 '19 at 17:12
7

The accepted answer does not work under Cygwin. Cygwin compiled programs running on Windows can use the Windows style '\' separator, but it does not define _WIN32 or the likes. A modified solution that works under Cygwin:

inline char separator()
{
#if defined _WIN32 || defined __CYGWIN__
    return '\\';
#else
    return '/';
#endif
}

or

const char kPathSeparator =
#if defined _WIN32 || defined __CYGWIN__
    '\\';
#else
    '/';
#endif
Fruity Nutty
  • 71
  • 1
  • 3
  • Cygwin is however guaranteed to recognize '/' so that shouldn't be a real issue. So the answer works just fine -- it will use the '/' character and will work without any issue at all. – Paul Stelian Aug 07 '20 at 21:07
3

std::filesystem::path::preferred_separator should now be used as a clean C++17 library function how it seems.

BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
2

I'm surprised no one has offered the following. This builds a bit on what others are offering here.

Although In this example I'm trying to dynamically grab the name of the executable being run for usage, it wouldn't be too hard to make the jump and reapply this however you need.

Windows uses forward slash to denote arguments. So you could check for that first in the first argument argv[0], which contains the name of the program being run.

Note the following results in stripping the pathname previous of the last slash, leaving sepd as the filename of the program.

#include <string.h>
#include <stdio.h>

int main(int argc, char *argv[]){
//int a = 1
//int this = (a == 1) ? 20 : 30;  //ternary operator
//is a==1 ? If yes then 'this' = 20, or else 'this' = 30
    char *sepd = (strrchr(argv[0], '\/') != NULL) ? 
        strrchr(argv[0], '\/') : 
        strrchr(argv[0], '\\');
    printf("%s\n\n", sepd);
    printf("usage: .%s <host> \n\n", sepd);
    while (getchar() != '\n');
}

But in all reality, this is pretty dirty and with Windows' most recent move to include Bash (not yet implemented at this time), this may produce unexpected or unanticipated results.

It's also not as sane and impervious to errors as what others have offered, particularly #ifdef _WIN32.

SYANiDE
  • 187
  • 7
  • 3
    Some programs on Windows also use `-` and `/` may be used on non-windows platforms. You're also not guaranteed that `argv[0]` contains anything at all and when it does, it may or may not contain anything besides the name of the process that started the program -- which may be unrelated to your program. – Clearer Jun 19 '18 at 12:06
  • 1
    @Clearer: You're right that there's no guarantee about `argv[0]` having anything useful. Additionally, argument delimiter characters are entirely up to the application. While Microsoft's commands use `/` and lots of apps (especially those ported from Unix) use `-`, they could be literally anything the app developer wants to use. – David C. Sep 15 '20 at 17:02