Possible reason of Path.Combine omitting one of parameters - c#

I noticed that my code writes files to incorrect folders, so I added some logging and the result amazes me. My code:
string savePath = Path.Combine(mapping.Folder, fileMeta.RelativePath, fileMeta.Filename);
logger.Info(mapping.Folder);
logger.Info(fileMeta.RelativePath);
logger.Info(fileMeta.Filename);
logger.Info("savepath: {0}", savePath);
This is, what has been written to log:
C:\Sync
\0100_MACHINES
Layout US.pdf
savepath: \0100_MACHINES\Layout US.pdf
Can anyone give possible reason, why Path.Combine omits first argument, and just combines second and third? What is also funny, that happened only on one machine.

Second parameter starts with backslash character. This caused Path.Combine to omit the part before. Solved this problem by adding TrimStart:
string savePath = Path.Combine(mapping.Folder,
fileMeta.RelativePath.TrimStart('\\'),
fileMeta.Filename.TrimStart('\\'));

If path2 does not include a root (for example, if path2 does not start with a separator character or a drive specification), the result is a concatenation of the two paths, with an intervening separator character. If path2 includes a root, path2 is returned.
https://msdn.microsoft.com/en-us/library/dd784047(v=vs.110).aspx
Seems like C# is treating relative folder as root path, thus ignoring path1.

Related

C# combining absolute and relative paths, Path.GetFullPath() inconsistent

I notice that when I use Path.GetFullPath(Path.Combine(#"c:\folder\","\subfolder\file.txt")) it returns a path c:\subfolder\file.txt, instead of the expected combined path c:\folder\subfolder\file.txt. It seems like having a '\' on the second combined input isn't handled by these methods. Has anyone come across this and found a better solution for merging paths?
var test1 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #".\test1.txt"));
var test2 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"\test2.txt"));//
var test3 = Path.GetFullPath(Path.Combine(#"C: \users\dev\desktop\testfiles\", #"test3.txt"));
var test4 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\",#".\XXX\test4.txt"));
var test5 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"\XXX\test5.txt"));//
var test6 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"XXX\test6.txt"));
var test7 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"\somefile\that\doesnt\exist.txt"));//
Results in
test1 is "C:\users\dev\desktop\testfiles\test1.txt"
test2 is wrong "\test2.txt"
test3 is "C: \users\dev\desktop\testfiles\test3.txt"
test4 is "C:\users\dev\desktop\testfiles\XXX\test4.txt"
test5 is wrong "c:\XXX\test5.txt"
test6 is "C:\users\dev\desktop\testfiles\XXX\test6.txt"
test7 is wrong "c:\somefile\that\doesnt\exist.txt"
Basically what I'm doing is combining a source path with individual file paths from a text file so that I can copy these files to a destination, and I want to maintain subfolder hierarchy.
e.g. common scenario of the files list being the same, but wanting to reference a specific source folder version.
source path + individual file path
\\myserver\project\1.x + \dll\important.dll
which should be copied to a destination in the same manner
c:\myproject\ + \dll\important.dll
So there would be variables; source, destination, files[array or list]
My current way forward is to use String.TRIM() method to remove special chars . \ / before combining paths with Path.Combine, Path.GetFullPath. But I thought maybe someone has realized a better way for a similar scenario.
It's also a pain creating the subfolders in the destination, basically im using String.LastIndexOf(#'\') to separate the filename from the foldername. Which is also seems a little sketchy, but a better way is beyond my experience.
Similar question:
Path.Combine absolute with relative path strings
Thanks to the feedback I now know that \file.txt is considered a fully qualified (absolute) path, which is why the combine method works that way.
A file name is relative to the current directory if it does not begin with one of the following:
A UNC name of any format, which always start with two backslash characters ("\\"). For more information, see the next section.
A disk designator with a backslash, for example "C:\" or "d:\".
A single backslash, for example, "\directory" or "\file.txt". This is also referred to as an absolute path.
https://msdn.microsoft.com/en-nz/library/windows/desktop/aa365247(v=vs.85).aspx#fully_qualified_vs._relative_paths
Based on the MSDN Documentation, this is expected behavior.
If path2 does not include a root (for example, if path2 does not start
with a separator character or a drive specification), the result is a
concatenation of the two paths, with an intervening separator
character. If path2 includes a root, path2 is returned.
Seperator characters are discussed in this MSDN article.
So let's take a look at your test cases:
var test1 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #".\test1.txt"));
var test2 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"\test2.txt"));
Test1 is behaving correctly because it is taking the . as part of a valid file path and using it in combination with the first parameter to generate the combined path.
Test2 is behaving as expected because the \ is a separator character, so as described it is returned without the first parameter.
There is no functions that you like to use in .Net framework - trimming path or ensuring that relative path starts with ".\" your real options (probably library that does it exists somewhere).
Why code does not behave the way you like (not "wrong", just not meeting your unexpected expectations).
Combine need to decide which part of left path will stay and which will be replaced. It uses regular file system conventions to decide that (".", "..", "\" have special meaning for file paths)
Meaning of path starting with:
#"\" or"/" - starting from the root of current device - this covers most cases that you don't like
#".\" - means from current directory (hence after combining and normalizing it will behave exactly as you want)
`#"..\" (possible multiple "go up") - go up one folder - this is another case you'll likely disagree with.
any character that is not '/' or #'\' - file name or path from current folder
Also note that path ending on anything but "/" or #"\" is treated by Path.Combine as file path and when combined with other path it will lose last segment.

Directory.GetParent and slashes

Running this snippet
var path1 = #"C:\Temp\SomeFolder";
var path2 = #"C:\Temp\SomeFolder\";
Console.WriteLine(Directory.GetParent(path1));
Console.WriteLine(Directory.GetParent(path2));
Output
C:\Temp
C:\Temp\SomeFolder
Same story with Path.GetDirectoryName(), which also can be used to obtain parent folder, but will similarly fail in case of \ at the end.
I do really like Path.Combine() for ignoring slashes at the end and do really hate some guys who make some methods returning path with slash (referring to AppDomain.CurrentDomain.BaseDirectory *angryface*).
Question: how to properly handle possible slash at the end of given path when I need to get one of the parent directories?
Here is another snippet
var path1 = #"C:\Temp\SomeFolder";
var path2 = #"C:\Temp\SomeFolder\";
var dir1 = #"Test";
var dir2 = #"Test\";
Console.WriteLine(Path.Combine(path1, dir1));
Console.WriteLine(Path.Combine(path1, dir2));
Console.WriteLine(Path.Combine(path2, dir1));
Console.WriteLine(Path.Combine(path2, dir2));
Output
C:\Temp\SomeFolder\Test
C:\Temp\SomeFolder\Test\
C:\Temp\SomeFolder\Test
C:\Temp\SomeFolder\Test\
Path.Combine works properly (ignoring ending slash of the first path, or well, adding it when it's missing), while persisting ending slash of second path (for whatever reasons, I do not care, because using resulting path in more Path.Combines will works as well).
As I understand Test and Test\ pointing at the same folder in directory structure. And if I want to get parent (previous folder), then Directory.GetParent should return me previous folder, and not Test folder again.
Question: how to properly handle possible slash at the end of given
path when I need get one of the parent directories?
Just trim it from the input string. Use TrimEnd:
Console.WriteLine(Directory.GetParent(path1.TrimEnd('\\')));
However returning the complete path when the string is terminated by \ is desired behaviour
Directory.GetParent Method
However, passing "C:\Directory\SubDirectory\" returns
"C:\Directory\SubDirectory", because the ending directory separator is
after "SubDirectory".
Some Path theory:
A file system contains Containers (Folders, Directories) and Elements (Files).
A "Path" Contains a set of (hierarchical) Containers and probably an "Element" as last element, all separated by slashes.
In theory, if the "Element" is missing, there is a trailing slash to indicate that the last item is a Container:
/Directory/Subdirectory/
And a Path without trailing slash points to an "Element", not an "Container"
/Directory/Subdirectory/Element
This is the theoretically clean way to handle things. Because it is often too cumbersome for end-users to deal with clean theory, the software often tolerates a missing trailing slash after a final "Container":
/Directory/Subdirectory
This is just a "hack" to make the life of the end-user easier and basically agiuants the theoretically sound specs for pathes.
Edit
This convention has its drawbacks and introduces missbehaviour at different places.
Look at the Directory.GetParent() method. It accepts a string and works with string manipulations only. So this method can not decide if the last item is a "Container" or an "Element" if the trailing slash is missing. It assumes to see an "Element" as last element and returns the next "Container". If the last item happens to be a "Container" (and we mean "all content in this container") this is indeed the wrong answer.

System.ArgumentException: Illegal characters in path. error

I`m getting an ArgumentException from the following code:
string strPath="C:\somename.xls";
startPath=System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
filePath = System.IO.Path.Combine(startPath, strPath);
I found this code on Stack Overflow.
Link:
C#:Copy protected worksheet to another excel file
I don't exactly know what it is. Please tell me what it is. This code I'm building into an exe.
Lastly, I need to Copy one worksheet to another file.
What`s wrong am I doing? I deploy this in server.
what that code appears to do, is it gets your working directory(wherever the exe associated with your code is), and combines it with "C:\\somename.xls"(which doesn't make sense.)
I think you might have intended something like
string strPath=#"somename.xls";
so assume you're running your application from
"C:\Users\owner\documents\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\bin\Debug"
what that code would do is set filePath to
"C:\Users\owner\documents\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\bin\Debug\somename.xls"
the first thing I saw was
string filePath="C:\somename.xls";
\ is a special character, for determining other characters. for instance '\n' is a newline. '\\' is the actual backslash.
so, you want to escape your \ with another \
string filePath="C:\\somename.xls";
or make it a literal string by putting a # in front of it.
string filePath=#"C:\somename.xls";
Your code should be:
string filePath = "C:\\somename.xls"
You need double backslashes.
Two problems with the code,
First
string filePath="C:\somename.xls";
\ is a special character, for determining other characters. for instance '\n' is a newline. '\\' is the actual backslash.
Second
filePath contains a root path, C:\\. Path.Combine will just return filePath then, it cannot be combined.
Your main problem is in startPath parameter.
System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName
If you trace your code, in FileName you will see a bad symbol character witch is illegal

How do I find the parent directory in C#?

I use this code for finding the debug directory
public string str_directory = Environment.CurrentDirectory.ToString();
"C:\\Users\\Masoud\\Documents\\Visual Studio 2008\\Projects\\MyProj\\MyProj\\bin\\Debug"
How can I find the parent folder as shown below?
"C:\\Users\\Masoud\\Documents\\Visual Studio 2008\\Projects\\MyProj\\MyProj"
You can use System.IO.Directory.GetParent() to retrieve the parent directory of a given directory.
string parent = System.IO.Directory.GetParent(str_directory).FullName;
See BOL
If you append ..\.. to your existing path, the operating system will correctly browse the grand-parent folder.
That should do the job:
System.IO.Path.Combine("C:\\Users\\Masoud\\Documents\\Visual Studio 2008\\Projects\\MyProj\\MyProj\\bin\\Debug", #"..\..");
If you browse that path, you will browse the grand-parent directory.
Edit: The normalization covered in this answer only happens when the path is used to access the file system, but not on the string itself. By contrast, this answer achieves the result and normalization purely using path strings, without using the file system at all.
I've found variants of System.IO.Path.Combine(myPath, "..") to be the easiest and most reliable. Even more so if what northben says is true, that GetParent requires an extra call if there is a trailing slash. That, to me, is unreliable.
Path.Combine makes sure you never go wrong with slashes.
.. behaves exactly like it does everywhere else in Windows. You can add any number of \.. to a path in cmd or explorer and it will behave exactly as I describe below.
Some basic .. behavior:
If there is a file name, .. will chop that off:
Path.Combine(#"D:\Grandparent\Parent\Child.txt", "..") => D:\Grandparent\Parent\
If the path is a directory, .. will move up a level:
Path.Combine(#"D:\Grandparent\Parent\", "..") => D:\Grandparent\
..\.. follows the same rules, twice in a row:
Path.Combine(#"D:\Grandparent\Parent\Child.txt", #"..\..") => D:\Grandparent\
Path.Combine(#"D:\Grandparent\Parent\", #"..\..") => D:\
And this has the exact same effect:
Path.Combine(#"D:\Grandparent\Parent\Child.txt", "..", "..") => D:\Grandparent\
Path.Combine(#"D:\Grandparent\Parent\", "..", "..") => D:\
To get a 'grandparent' directory, call Directory.GetParent() twice:
var gparent = Directory.GetParent(Directory.GetParent(str_directory).ToString());
Directory.GetParent is probably a better answer, but for completeness there's a different method that takes string and returns string: Path.GetDirectoryName.
string parent = System.IO.Path.GetDirectoryName(str_directory);
Like this:
System.IO.DirectoryInfo myDirectory = new DirectoryInfo(Environment.CurrentDirectory);
string parentDirectory = myDirectory.Parent.FullName;
Good luck!
No one has provided a solution that would work cross-form. I know it wasn't specifically asked but I am working in a linux environment where most of the solutions (as at the time I post this) would provide an error.
Hardcoding path separators (as well as other things) will give an error in anything but Windows systems.
In my original solution I used:
char filesep = Path.DirectorySeparatorChar;
string datapath = $"..{filesep}..{filesep}";
However after seeing some of the answers here I adjusted it to be:
string datapath = Directory.GetParent(Directory.GetParent(Directory.GetCurrentDirectory()).FullName).FullName;
You might want to look into the DirectoryInfo.Parent property.
IO.Path.GetFullPath(#"..\..")
If you clear the "bin\Debug\" in the Project properties -> Build -> Output path, then you can just use AppDomain.CurrentDomain.BaseDirectory
Since nothing else I have found helps to solve this in a truly normalized way, here is another answer.
Note that some answers to similar questions try to use the Uri type, but that struggles with trailing slashes vs. no trailing slashes too.
My other answer on this page works for operations that put the file system to work, but if we want to have the resolved path right now (such as for comparison reasons), without going through the file system, C:/Temp/.. and C:/ would be considered different. Without going through the file system, navigating in that manner does not provide us with a normalized, properly comparable path.
What can we do?
We will build on the following discovery:
Path.GetDirectoryName(path + "/") ?? "" will reliably give us a directory path without a trailing slash.
Adding a slash (as string, not as char) will treat a null path the same as it treats "".
GetDirectoryName will refrain from discarding the last path component thanks to the added slash.
GetDirectoryName will normalize slashes and navigational dots.
This includes the removal of any trailing slashes.
This includes collapsing .. by navigating up.
GetDirectoryName will return null for an empty path, which we coalesce to "".
How do we use this?
First, normalize the input path:
dirPath = Path.GetDirectoryName(dirPath + "/") ?? ""; // To handle nulls, we append "/" rather than '/'
Then, we can get the parent directory, and we can repeat this operation any number of times to navigate further up:
// This is reliable if path results from this or the previous operation
path = Path.GetDirectoryName(path);
Note that we have never touched the file system. No part of the path needs to exist, as it would if we had used DirectoryInfo.
To avoid issues with trailing \, call it this way:
string ParentFolder = Directory.GetParent( folder.Trim('\\')).FullName;
To get your solution
try this
string directory = System.IO.Directory.GetParent(System.IO.Directory.GetParent(Environment.CurrentDirectory).ToString()).ToString();
This is the most common way -- it really depends on what you are doing exactly:
(To explain, the example below will remove the last 10 characters which is what you asked for, however if there are some business rules that are driving your need to find a specific location you should use those to retrieve the directory location, not find the location of something else and modify it.)
// remove last 10 characters from a string
str_directory = str_directory.Substring(0,str_directory.Length-10);
You shouldn't try to do that. Environment.CurrentDirectory gives you the path of the executable directory. This is consistent regardless of where the .exe file is. You shouldn't try to access a file that is assumed to be in a backwards relative location
I would suggest you move whatever resource you want to access into a local location. Of a system directory (such as AppData)
In my case I am using .NET 6. When I use:
public string str_directory = Environment.CurrentDirectory.ToString();
returns C:\Projects\MyTestProject\bin\Debug\net6.0
In order to reach C:\Projects\MyTestProject I am using the following:
Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent
You can chain Parent to Directory.GetParent(Environment.CurrentDirectory) in order to reach the directory you want.
Final version:
public string str_directory = Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.ToString();

How to return a verbatim string from ConfigurationManager.AppSetting["settingname"].ToString()

I am using the ConfigurationManager.AppSetting["blah"].ToString() method to get the path to the folder that contains the files I'm needing. But I'm throwing an UnsupportedFormatException on the path when it tries to use Directory.GetFiles(path).
The returning value has the escape characters included and I'm not sure how to keep it from returning the extra characters. This is what the path looks like after it is returned:
\\\\\\\\C:\\\\folder1\\\\folder2
I needed to remove the first four "\" to give it a correct path.
you have extra back-slash \ at the beginning of your path.
try putting "C:\folder1\folder2" instead of "\\C:\folder1\folder2" in your config file, and it will work.

Categories