I've got a string like "Foo: Bar" that I want to use as a filename, but on Windows the ":" char isn't allowed in a filename.
Is there a method that will turn "Foo: Bar" into something like "Foo- Bar"?
I've got a string like "Foo: Bar" that I want to use as a filename, but on Windows the ":" char isn't allowed in a filename.
Is there a method that will turn "Foo: Bar" into something like "Foo- Bar"?
Try something like this:
string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
Edit:
Since GetInvalidFileNameChars() will return 10 or 15 chars, it's better to use a StringBuilder instead of a simple string; the original version will take longer and consume more memory.
fileName = fileName.Replace(":", "-")
However ":" is not the only illegal character for Windows. You will also have to handle:
/, \, :, *, ?, ", <, > and |
These are contained in System.IO.Path.GetInvalidFileNameChars();
Also (on Windows), "." cannot be the only character in the filename (both ".", "..", "...", and so on are invalid). Be careful when naming files with ".", for example:
echo "test" > .test.
Will generate a file named ".test"
Lastly, if you really want to do things correctly, there are some special file names you need to look out for. On Windows you can't create files named:
CON, PRN, AUX, CLOCK$, NUL
COM0, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9
LPT0, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.
A simple one line code:
var validFileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
You can wrap it in an extension method if you want to reuse it.
public static string ToValidFileName(this string fileName) => Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
I needed a system that couldn't create collisions so I couldn't map multiple characters to one. I ended up with:
public static class Extension
{
/// <summary>
/// Characters allowed in a file name. Note that curly braces don't show up here
/// becausee they are used for escaping invalid characters.
/// </summary>
private static readonly HashSet<char> CleanFileNameChars = new HashSet<char>
{
' ', '!', '#', '$', '%', '&', '\'', '(', ')', '+', ',', '-', '.',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '=', '@',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'[', ']', '^', '_', '`',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
};
/// <summary>
/// Creates a clean file name from one that may contain invalid characters in
/// a way that will not collide.
/// </summary>
/// <param name="dirtyFileName">
/// The file name that may contain invalid filename characters.
/// </param>
/// <returns>
/// A file name that does not contain invalid filename characters.
/// </returns>
/// <remarks>
/// <para>
/// Escapes invalid characters by converting their ASCII values to hexadecimal
/// and wrapping that value in curly braces. Curly braces are escaped by doubling
/// them, for example '{' => "{{".
/// </para>
/// <para>
/// Note that although NTFS allows unicode characters in file names, this
/// method does not.
/// </para>
/// </remarks>
public static string CleanFileName(this string dirtyFileName)
{
string EscapeHexString(char c) =>
"{" + (c > 255 ? $"{(uint)c:X4}" : $"{(uint)c:X2}") + "}";
return string.Join(string.Empty,
dirtyFileName.Select(
c =>
c == '{' ? "{{" :
c == '}' ? "}}" :
CleanFileNameChars.Contains(c) ? $"{c}" :
EscapeHexString(c)));
}
}
There are no valid answers in this topic yet. Author said: "...I want to use as a filename...". Remove/replace invalid characters is not enough to use something as filename. You should at least check that:
Probably the best way would be to:
As always, things are more complicated, then they look. Better to use some already existing function, like GetTempFileNameW
Still another solution I am using for the last ~10 years, very similar to previous solutions, without the 'fancy' parts: The main method gets the specialcharacters as input, since I was using it also for other purposes, e.g. getting web compatible names, especially back then when renaming files for SharePoint/OneDrive
Not sure how much it improves the speed, but also chose to check the filename for any special characters BEFORE using the StringBuilder with IndexOfAny().
private static string SanitizeFilename(this string filename)
=> filename.RemoveOrReplaceSpecialCharacters(Path.GetInvalidFileNameChars(), '_');
private static string RemoveOrReplaceSpecialCharacters(this string str, char[] specialCharacters, char? replaceChar)
{
if (string.IsNullOrEmpty(str))
return str;
if (specialCharacters == null || specialCharacters.Length == 0)
return str;
if (str.IndexOfAny(specialCharacters) == 0)
return str;
var sb = new StringBuilder(str.Length);
foreach (char c in str)
{
if (!specialCharacters.Contains(c))
sb.Append(c);
else if (replaceChar.HasValue)
sb.Append(replaceChar.Value);
}
return sb.ToString();
}
I tried also
return new string(str.Except(specialCharacters).ToArray());
but it created strange behavior, where duplicate are ignored and further issue. For instance, "Bla-ID" became "BlaI" when specifying - as single special char.
You can do this with a sed command:
sed -e "
s/[?()\[\]=+<>:;©®”,*|]/_/g
s/"$'\t'"/ /g
s/–/-/g
s/\"/_/g
s/[[:cntrl:]]/_/g"