38

In an example:

var assets = "images/"

var sounds = assets+"sounds/"

Is it more conventional to put the slash on the back of a file path?

var assets = "/images"

var sounds = assets+"/sounds"

Is there another method that is a good common practice?

iiridescent
  • 499
  • 1
  • 4
  • 7
  • Java has the static Strings File.separator and File.pathSeparator which sounds relevant. This way you are safe across all platforms – Evorlor Jan 02 '15 at 17:50
  • 1
    @Evorlor You rarely need to use `File.separator` though, the `File` and `Path` APIs accept both `/` and `\\` . – kapex Jan 02 '15 at 18:28
  • 2
    Could you indicate which language you are using, please? It’s probably worth adding the corresponding tag. – Christopher Creutzig Jan 02 '15 at 23:21
  • @ChristopherCreutzig I'm using Java - although I was asking if there were any commonly used conventions for combining file directories in strings. Apparently there are a few generally accepted rules and some common sense is involved, but it varies a bit from language to language. – iiridescent Jan 03 '15 at 06:42
  • nb the first example is what I commonly use when I know the paths will later get sanitized anyway... –  Jan 03 '15 at 13:39
  • I think you need to clarify if you are referring to file system paths or URLs. Your example looks like a URL, but some of the answers are referring to the file system (probably because you refer to "file path" in the title). – MrWhite Jan 03 '15 at 19:11
  • 1
    For what it's worth, in the unix world (and in urls), multiple forward slashes in the middle of a path are treated identically to a single one, so nothing bad would happen if you err on the side of more slashes. It's part of the Single Unix Specification; see this answer - http://unix.stackexchange.com/a/1919/21161 – yoniLavi Jan 08 '15 at 17:38
  • Possible same as: http://stackoverflow.com/questions/412380/combine-paths-in-java – Ciro Santilli OurBigBook.com Apr 08 '15 at 16:00
  • Is it just me who wonders why this question is tagged java when the code given in it is quite clearly not java? – Jules Jul 25 '15 at 08:59

8 Answers8

40

Nearly every major programming language has a library to handle the directory separators for you. You should leverage them. This will simplify your code and prevent bugs.

In my experience, the usual reason for combining strings like this is that they come from different sources. Sometimes it's different pieces from a configuration file. Sometimes it's a constant combining with a function argument. In any and all cases, when they come from different sources, you have to consider several different possible cases regarding the separators on the ends to be combined:

  • Both ends could have a separator: "images/" and "/sounds"
  • Only one has a separator: "images" and "/sounds" or "images/" and "sounds"
  • Neither has a separator: "images" and "sounds"

The fact each part comes from a different source means each source might have its own ideas about what conventions to follow, if someone gave any thought to it at all! Whatever is calling your code should not have to worry about this. Your code should handle all cases because someone will violate your convention. This will result in wasted time investigating the cause of an error and making a fix. I have had several unpleasant occasions where a coworker made an assumption about how paths should be formatted in a configuration file, meaning I had to go hunt down the code and figure out what they were expecting (or fix the code).

Most major languages provide a method to do this for you that already handles many of the cases:

There is a caveat with these. A number of these seem to assume that a leading directory separator in the second argument refers to a root path and that this means the first argument should be dropped entirely. I don't know why this is considered useful; for me, it just causes problems. I've never wanted to combine two path portions and end up with the first part being dropped. Read the documentation carefully for special cases, and if necessary, write a wrapper that does what you want with these instead of their special handling.

This additionally helps if you have any need for supporting different operating systems. These classes almost ubiquitously account for choosing the correct separator. The libraries usually have a way of normalizing paths to fit the OS conventions, as well.

In the event that your programming language does not have a readily available library, you should write a method that handles all these cases and use it liberally and across projects.

This falls into the category of "don't make assumptions" and "use tools that help you."

Vorac
  • 7,073
  • 7
  • 38
  • 58
jpmc26
  • 5,389
  • 4
  • 25
  • 37
  • 2
    .NET's Path.Combine is not broken. Just don't feed it separators. make sure that you read the documentation, if the second argument is a root path it has a defined outcome. You might not like it but that doesn't mean it is broken. – Emond Jan 03 '15 at 07:35
  • 4
    Make sure you read the documentation to ensure it's not trying to be too clever. I once used a library that could successfully combine `C:\Documents and Settings\Admin` with `my folder:document.txt` on a *nix system to produce `/home/admin/my folder/document.txt` -- a cute trick, but in the real world, the heuristics involved introduced more bugs than they fixed. – Mark Jan 04 '15 at 01:08
  • @Erno Fair enough. I've done some checking, and apparently, .NET isn't the only one that does this. However, I cannot see how in the world this is useful. See my edit. – jpmc26 Jan 04 '15 at 17:38
  • @Thomas Just FYI, I've found that .NET isn't the only one with weird special cases like this and edited accordingly. As Mark says, check the documentation carefully, and if necessary, write some re-usable code that filters these special cases out and does what you really want. – jpmc26 Jan 04 '15 at 17:39
  • Regarding the joining of paths with leading slashes, I'd argue that this is expected behavior because on *nix systems, the leading slash shows the root directory. Thus, joining `foo` and `/bar` makes about as much as sense as joining `foo` and `C:/bar` on a Windows machine. So a path should NEVER start with a leading slash unless it's supposed to be from the root folder (in which case joining simply doesn't make sense and the library tries its best). – Kat Jan 06 '15 at 22:03
  • 1
    Also, for Java, `Paths.get()` just converts a single `String` into a `Path` object. To join paths, you'd use `Path.resolve()`, which can take in another `Path` or a `String`. There's other methods in the `Path` class that further allow for joining paths in various ways. – Kat Jan 06 '15 at 22:06
  • @Mike Throwing an error would be more appropriate than assuming the first part should discarded in the face of a leading slash, which makes even less sense on Windows. Assuming that it is extraneous would, imo, be less of an assumption than it actually referring to the root, since it's being fed into a join function. Regarding `Paths.get`, the docs say, "If `more` specifies one or more elements then each non-empty string, including `first`, is considered to be a sequence of name elements (see `Path`) and is joined to form a path string." So I'm not sure what you're referring to. – jpmc26 Jan 07 '15 at 00:32
  • 1
    My bad, it seems I didn't read the docs for `Paths` very well. – Kat Jan 07 '15 at 00:47
  • @jpmc26 the point of having the leading slash override the specified path is to allow the use case where a user is specifying a file name with an optional absolute path and a default base location is used if they don't specify an absolute one. In my experience this is a very common requirement, so it is unsurprising that it is supported by many systems. – Jules Jul 25 '15 at 08:54
  • @Jules This could easily be solved by running the path through an "is absolute" first and not joining if so. That would be far preferable, since the way it currently works makes it very easy to accidentally allow a user to specify and absolute path when you *don't* want them to. This can even be a security risk in some cases. It's a bad practice to shove this kind of special logic into a method that claims to be there solely for the purpose of combining partial paths. It's unexpected, unintuitive, and creates problems when it's the wrong guess about what callers need. – jpmc26 Jul 26 '15 at 05:54
  • The issues with "leading slashes in the second argument" highlights the fact that path manipulation often isn't about path **concatenation** but path **resolution**. Paths can be relative or absolute, and it only really makes sense to resolve a **relative** pathname against another. This can also help to make it clearer why final path separators (which might indicate whether a path denotes a regular file or a directory) are important too. To resolve, e.g., `baz` against `foo/bar/` might produce `foo/bar/baz`, but to resolve `baz` against `foo/bar` might produce `foo/baz`. – Joshua Taylor Jun 13 '16 at 21:02
  • @JoshuaTaylor I suppose I'm of the opinion that a `join` method should not engage in resolution, only concatenation. Anything else is more likely to result in unexpected behavior and to cause extra work trying to guard against a lot of edge cases. – jpmc26 Jun 13 '16 at 21:14
  • @jpmc26 Exactly. Some of these methods seem to be more for pathname resolution than for pure concatenation, and that causes unexpected outcomes (for users expecting pure concatenation, rather than resolution). – Joshua Taylor Jun 13 '16 at 21:44
  • 1
    On PowerShell, an alternative to the .NET method `[System.IO.Path]::Combine("abc", "\def")` which has the described behavior, is the cmdlet `Join-Path "abc" "\def"` which gives `"abc\def"`. – Jeppe Stig Nielsen May 05 '18 at 06:53
39

In Java, the answer would be "neither of the above". Best practice would be to assemble pathnames using the java.io.File class; e.g.

File assets = new File("images");
File sounds = new File(assets, "sounds");

The File class also takes care of platform-specific pathname separators.

There is a separate issue of whether your pathname should start with a slash or not. But that is more to do with correctness than best practice. A pathname that starts with a slash means something different to a pathname that doesn't!!


There isn't explicit support for pathname handling in the core (ECMA) Javascript library, but (at least) Node.js provides support via the Path module.

Stephen C
  • 25,180
  • 6
  • 64
  • 87
  • 4
    Something similar is also the case for the .Net Framework languages and any others that offer filesystem classes. – James Snell Jan 02 '15 at 10:50
  • 3
    Thank you! This seemed like the most helpful answer, even though language specific, libraries should exist for other languages in general, like .NET and C++; – iiridescent Jan 02 '15 at 15:16
  • 3
    Really, any code that does not use a library should be rejected in code review. In the rare chance no library exists, the answer would be to write one yourself rather than pasting raw strings. – Gort the Robot Jan 02 '15 at 17:10
  • C++ has [Boost::Filesystem](http://www.boost.org/doc/libs/1_57_0/libs/filesystem/doc/index.htm), and C# has [System.IO.Path](http://msdn.microsoft.com/en-us/library/system.io.path%28v=vs.110%29.aspx) – Mooing Duck Jan 02 '15 at 21:08
  • Python has `os.path.join`. PowerShell has `join-path`. I would add a something to this answer. I have found that if you need file paths in multiple pieces, it makes your code *very* fragile if you make assumptions about any of them having file paths in particular places. Using these classes not only helps with portability, but it also handles all the possible edge cases (slash on both ends to be joined, slash on only one side, no slash between at all). This flexibility is *invaluable* when you're dropping file paths in a configuration file. – jpmc26 Jan 02 '15 at 23:37
  • @jpmc26 Also, it's worth noting that Python 3.4 introduced a new [`pathlib`](https://docs.python.org/3.4/library/pathlib.html) module to give a more OO feel to the old `os.path`. – Morwenn Jan 02 '15 at 23:50
21

Note that in .NET you should use the Path.Combine method.

var path = System.IO.Path.Combine("assets", "sounds");

The reason for this is that it 'knows' the correct characters to be used when constructing the folder names.

This takes away the 'problem' of pre or post fixing.

Emond
  • 1,248
  • 8
  • 13
  • 4
    os.path.join does basically the same thing for python, too – Weaver Jan 02 '15 at 15:39
  • Note that path.combine does not get you out of the business of worrying about the seperator: http://stackoverflow.com/questions/53102/why-does-path-combine-not-properly-concatenate-filenames-that-start-with-path-di – jmoreno Jan 02 '15 at 16:31
  • 1
    @jmoreno - In my example there are NO separators. The question you linked to has hard coded separators and if fundamentally wrong because the second path is an absolute path. – Emond Jan 02 '15 at 16:49
  • Be careful with this, though. I'm unsure about .NET, but `os.path.join('src', '../../../your_secret_stuff')` *is* valid in Python; in other words, don't blindly use these methods on user input. – sapi Jan 02 '15 at 23:34
  • @sapi - Of course, user input should always be sanitized but that is the responsibility of the programmer, not of the API. – Emond Jan 03 '15 at 07:32
5

When building paths I often use a function that adds the trailing slash if it isn't already there. Then paths can be built like:

filename := fs( 'assets') + fs( 'images') + fs( 'icons') + 'some.png';

where fs() adds a trailing slash if its needed.

GrandmasterB
  • 37,990
  • 7
  • 78
  • 131
5

Folders and files differ only in one aspect: folders end with a slash where files do not. Furthermore, absolute paths begin with a / where relative paths do not. If you use this consistently concatenating paths and files together should be no problem.

var absolutepath = "/my/path/";
var relativepath = "css/";
var filename = "test.css";
var relativepathtofilename = "js/test.js";

var a = absolutepath + relativepath + filename; //Output: /my/path/css/test.css
var b = absolutepath + relativepathtofilename;  //Output: /my/path/js/test.js

Concatenating two absolute paths together makes no sense, since the second path should be relative to the first path. Concatenating two relative paths together is no problem, but might lead to undefined behavior if the program does not know where the relative path is relative to.

Sumurai8
  • 153
  • 5
  • This probably answered my original question best, I think I understand file paths better, though like Stephen C and Erno said, language libraries are a best first bet. This explains convention better though. Thank you! – iiridescent Jan 02 '15 at 18:40
  • File system paths or URLs? – MrWhite Jan 03 '15 at 19:22
  • 1
    For all intents and purposes, you can apply this on uri's as well. An absolute uri would start with a protocol, but besides that this would be the same I think. – Sumurai8 Jan 03 '15 at 23:18
  • Not sure how your output is working. When I do it I get: `var a = "/my/path" + "css/" + "test.css"; //Output: "/my/pathcss/test.css"` – Damon Jun 13 '16 at 14:46
  • 1
    @Damon I made an edit. `absolutepath` should have ended with a slash, because it is a path. Somehow I overlooked that when I wrote this. – Sumurai8 Jun 13 '16 at 15:59
4

I think there is no magic or "common practice" on how to implement paths, but certainly string concatenation is not the way to go. You can develop your own API for dealing with cases, but it may require some effort. In particular, you should be careful about different platforms. For example, in Windows \ is the separator while in Unix-based systems / is the separator.

I'm not familiar with Javascript libraries, but I'm sure there should be libraries for handling these cases. In Java, for example, you can use the Path API to deal with platform independent path operations.

Uyghur Lives Matter
  • 126
  • 1
  • 1
  • 10
Wickoo
  • 234
  • 1
  • 3
  • 3
    Windows actually does support `/` as path filename delimiter. This does need quirks in the command line, but file I/O APIs work nicely with forward slash. – Ruslan Jan 02 '15 at 12:57
  • http://en.wikipedia.org/wiki/Path_%28computing%29#MS-DOS.2FMicrosoft_Windows_style "the Windows system API accepts slash, and thus all the above Unix examples should work. But many applications on Windows interpret a slash for other purposes or treat it as an invalid character, and thus require you to enter backslash — notably the cmd.exe shell (often called the "terminal" as it typically runs in a terminal window)." – Mooing Duck Jan 02 '15 at 21:12
0

My personal preference is this:

var assets = "/images"

var sounds = assets+"/sounds"

I always use absolute paths (/images/...), it feels less prone to error, to me. It is also more fool proof to use var sounds = assets+"/sounds" because even if assets had a trailing slash and you ended up with /images//sounds, it would still resolve to /images/sounds. The one disclaimer being that it depends on your request handler. Apache seems to handle it fine (at least certain versions/configurations, see http://www.amazon.com//gp//site-directory//ref=nav_sad). With the other way you'd end up with /imagessounds, not so fool proof :) There is also the option of checking for double slashes and cleaning them up. Not an option with the other approach.

rpaskett
  • 166
  • 1
  • 9
  • 11
    In all contexts that I know of, a path that starts with a slash (`/`) is an *absolute* path, not a relative path. Or did you mean it only for path-sections other than the first one? – Bart van Ingen Schenau Jan 02 '15 at 09:08
  • @BartvanIngenSchenau I totally agree with you and I've been calling them that for years, but every time I read an article written by a front end developer they refer to them as relative paths. I didn't want to make assumptions so I guess I picked the lesser of two evils...? Now that I know I have some folks on my side I'll update my answer :) – rpaskett Jan 02 '15 at 16:31
  • 2
    For web developers, `/somewhere` is a relative path because it doesn't include the host, so the browser will look it up based on the current page's host... In the web world, `http://here/somewhere` is an absolute URI, and `/somewhereelse` is relative to that. In the file-system world, `/somewhere` is absolute, coming from root `/`, and "somewhereelse" is relative to the current working directory. – sea-rob Jan 02 '15 at 16:57
  • ...note that to confuse things even more for the web world, these are all valid paths, in increasing order of relativeness ;) `http://here/somewhere`, `/somwhereelse`, `altogethersomewhereelse` – sea-rob Jan 02 '15 at 17:00
  • 3
    @RobY, rpaskett: Going by [RFC3986](https://tools.ietf.org/html/rfc3986)(the RFC that defines URIs), `http://here/somewhere` is an URI with an absolute path, `/somewhere` is a relative reference with an absolute path and `somewhere/else` is a relative reference with a relative path. Apparently, in those circles "relative path" is used to refer to a relative reference. – Bart van Ingen Schenau Jan 02 '15 at 17:05
  • 1
    @BartvanIngenSchenau: in windows, a path that starts with a slash is a relative path, and is relative to CWD. http://en.wikipedia.org/wiki/Path_%28computing%29#Representations_of_paths_by_operating_system_and_shell – Mooing Duck Jan 02 '15 at 21:16
  • There would seem to be some confusion between file system paths and URLs. – MrWhite Jan 03 '15 at 19:24
0

In Smalltalk it is straightforward to define the / method in String so that it works like this:

'assets' / 'sounds' => 'assets/sounds'.
'assets/' / 'sounds' => 'assets/sounds'.
'assets' / '/sounds' => 'assets/sounds'.
'assets/' / '/sounds' => 'assets/sounds'.

Here is a simple implementation of the method (you can make it better):

/ aString
    | slash first second |
    slash := Directory separator.
    first := self.
    (first endsWith: slash) ifTrue: [first := first allButLast].
    second := aString.
    (second beginsWith: slash) ifTrue: [second := second allButFirst].
    ^first , slash , second

Note: you might also want to pay better attention to border cases such as '' / '', 'x/' / '', etc., in order to determine the proper behavior.