Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure there is trailing directory separator in paths?

Tags:

c#

.net

path

People also ask

Which character is used as a path separator?

pathSeparator would be ; . File. separator is either / or \ that is used to split up the path to a specific file. For example on Windows it is \ or C:\Documents\Test.

Which one of the followings is directory separator character on Windows?

Microsoft chose the backslash character ("\") as a directory separator, which looks similar to the slash character, though more modern version of Windows are slash-agnostic, allowing mixage of both types of slashes in a path.

Which is the correct constant for a platform independent directory separator?

If you prefer to hard-code the directory separator character, you should use the forward slash ( / ) character. It is the only recognized directory separator character on Unix systems, as the output from the example shows, and is the AltDirectorySeparatorChar on Windows.


You can easily ensure the behaviour you desire by using TrimEnd:

var baseDir = AppDomain.CurrentDomain.BaseDirectory
                  .TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;

To be optimally efficient (by avoiding extra allocations), check that the string doesn't end with a \ before making changes, since you won't always need to:

const string sepChar = Path.DirectorySeparatorChar.ToString();
const string altChar = Path.AltDirectorySeparatorChar.ToString();

var baseDir = AppDomain.CurrentDomain.BaseDirectory;
if (!baseDir.EndsWith(sepChar) && !baseDir.EndsWith(altChar))
{
    baseDir += sepChar;
}

It's like that, just keep your hack.

In plain Win32 there is an helper function PathAddBackslash for that. Just be consistent with directory separator: check Path.DirectorySeparatorChar and Path.AltDirectorySeparatorChar instead of hard-code \.

Something like this (please note there is not a serious error checking):

string PathAddBackslash(string path)
{
    // They're always one character but EndsWith is shorter than
    // array style access to last path character. Change this
    // if performance are a (measured) issue.
    string separator1 = Path.DirectorySeparatorChar.ToString();
    string separator2 = Path.AltDirectorySeparatorChar.ToString();

    // Trailing white spaces are always ignored but folders may have
    // leading spaces. It's unusual but it may happen. If it's an issue
    // then just replace TrimEnd() with Trim(). Tnx Paul Groke to point this out.
    path = path.TrimEnd();

    // Argument is always a directory name then if there is one
    // of allowed separators then I have nothing to do.
    if (path.EndsWith(separator1) || path.EndsWith(separator2))
        return path;

    // If there is the "alt" separator then I add a trailing one.
    // Note that URI format (file://drive:\path\filename.ext) is
    // not supported in most .NET I/O functions then we don't support it
    // here too. If you have to then simply revert this check:
    // if (path.Contains(separator1))
    //     return path + separator1;
    //
    // return path + separator2;
    if (path.Contains(separator2))
        return path + separator2;

    // If there is not an "alt" separator I add a "normal" one.
    // It means path may be with normal one or it has not any separator
    // (for example if it's just a directory name). In this case I
    // default to normal as users expect.
    return path + separator1;
}

Why so much code? Primary because if user enter /windows/system32 you don't want to get /windows/system32\ but /windows/system32/, devil is in the details...

To put everything together in a nicer self-explicative form:

string PathAddBackslash(string path)
{
    if (path == null)
        throw new ArgumentNullException(nameof(path));

    path = path.TrimEnd();

    if (PathEndsWithDirectorySeparator())
        return path;

    return path + GetDirectorySeparatorUsedInPath();

    bool PathEndsWithDirectorySeparator()
    {
        if (path.Length == 0)
            return false;

        char lastChar = path[path.Length - 1];
        return lastChar == Path.DirectorySeparatorChar
            || lastChar == Path.AltDirectorySeparatorChar;
    }

    char GetDirectorySeparatorUsedInPath()
    {
        if (path.Contains(Path.AltDirectorySeparatorChar))
            return Path.AltDirectorySeparatorChar;

        return Path.DirectorySeparatorChar;
    }
}

URI format file:// is not handled even if it may seem so. The right thing is again to do what the other .NET I/O functions do: do not handle this format (and possibly throw an exception).

As alternative you're always able to import Win32 function:

[DllImport("shlwapi.dll", 
    EntryPoint = "PathAddBackslashW",
    SetLastError = True,
    CharSet = CharSet.Unicode)]
static extern IntPtr PathAddBackslash(
    [MarshalAs(UnmanagedType.LPTStr)]StringBuilder lpszPath);

I often use

path = Path.Combine(path, "x");
path = path.Substring(0, path.Length - 1);

Or, if I needed this more than once or twice in the same project, I'd probably use a helper function like this:

string EnsureTerminatingDirectorySeparator(string path)
{
    if (path == null)
        throw new ArgumentNullException("path");

    int length = path.Length;
    if (length == 0)
        return "." + Path.DirectorySeparatorChar;

    char lastChar = path[length - 1];
    if (lastChar == Path.DirectorySeparatorChar || lastChar == Path.AltDirectorySeparatorChar)
        return path;

    int lastSep = path.LastIndexOfAny(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
    if (lastSep >= 0)
        return path + path[lastSep];
    else
        return path + Path.DirectorySeparatorChar;
}

In order to get cross platform support one can use this snippet:

using System.IO;

// Your input string.
string baseDir = AppDomain.CurrentDomain.BaseDirectory;

// Get the absolut path from it (in case ones input is a relative path).
string fullPath = Path.GetFullPath(baseDir);

// Check for ending slashes, remove them (if any)
// and add a cross platform slash at the end.
string result = fullPath
                    .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
                    + Path.DirectorySeparatorChar;

As a method:

private static string GetFullPathWithEndingSlashes(string input)
{
    string fullPath = Path.GetFullPath(input);

    return fullPath
        .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
        + Path.DirectorySeparatorChar;
}

Or as an extension method:

public static string GetFullPathWithEndingSlashes(this string input)
{
    return Path.GetFullPath(input)
        .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
        + Path.DirectorySeparatorChar;
}