Mam ciąg, taki jak „Foo: Bar”, którego chcę użyć jako nazwy pliku, ale w systemie Windows znak „:” nie jest dozwolony w nazwie pliku.
Czy istnieje metoda, która zamieni „Foo: Bar” w coś takiego jak „Foo-Bar”?
Mam ciąg, taki jak „Foo: Bar”, którego chcę użyć jako nazwy pliku, ale w systemie Windows znak „:” nie jest dozwolony w nazwie pliku.
Czy istnieje metoda, która zamieni „Foo: Bar” w coś takiego jak „Foo-Bar”?
Odpowiedzi:
Spróbuj czegoś takiego:
string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
Edytować:
Ponieważ GetInvalidFileNameChars()
zwróci 10 lub 15 znaków, lepiej jest użyć a StringBuilder
zamiast prostego ciągu; oryginalna wersja potrwa dłużej i zużyje więcej pamięci.
file.name.txt.pdf
jest prawidłowym plikiem PDF. Windows odczytuje tylko ostatnie .
rozszerzenie.
fileName = fileName.Replace(":", "-")
Jednak „:” nie jest jedynym niedozwolonym znakiem w systemie Windows. Będziesz musiał również poradzić sobie z:
/, \, :, *, ?, ", <, > and |
Są one zawarte w System.IO.Path.GetInvalidFileNameChars ();
Również (w systemie Windows) „.” nie może być jedynym znakiem w nazwie pliku (oba „.”, „..”, „...” itd. są nieprawidłowe). Zachowaj ostrożność podczas nazywania plików za pomocą „.”, Na przykład:
echo "test" > .test.
Wygeneruje plik o nazwie „.test”
Na koniec, jeśli naprawdę chcesz zrobić coś poprawnie, istnieje kilka specjalnych nazw plików, na które musisz zwrócić uwagę. W systemie Windows nie można tworzyć plików o nazwach:
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.
To nie jest bardziej wydajne, ale jest fajniejsze :)
var fileName = "foo:bar";
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
var cleanFileName = new string(fileName.Where(m => !invalidChars.Contains(m)).ToArray<char>());
Jeśli ktoś chce mieć zoptymalizowaną wersję StringBuilder
, użyj tego. Zawiera sztuczkę rkagerera jako opcję.
static char[] _invalids;
/// <summary>Replaces characters in <c>text</c> that are not allowed in
/// file names with the specified replacement character.</summary>
/// <param name="text">Text to make into a valid filename. The same string is returned if it is valid already.</param>
/// <param name="replacement">Replacement character, or null to simply remove bad characters.</param>
/// <param name="fancy">Whether to replace quotes and slashes with the non-ASCII characters ” and ⁄.</param>
/// <returns>A string that can be used as a filename. If the output string would otherwise be empty, returns "_".</returns>
public static string MakeValidFileName(string text, char? replacement = '_', bool fancy = true)
{
StringBuilder sb = new StringBuilder(text.Length);
var invalids = _invalids ?? (_invalids = Path.GetInvalidFileNameChars());
bool changed = false;
for (int i = 0; i < text.Length; i++) {
char c = text[i];
if (invalids.Contains(c)) {
changed = true;
var repl = replacement ?? '\0';
if (fancy) {
if (c == '"') repl = '”'; // U+201D right double quotation mark
else if (c == '\'') repl = '’'; // U+2019 right single quotation mark
else if (c == '/') repl = '⁄'; // U+2044 fraction slash
}
if (repl != '\0')
sb.Append(repl);
} else
sb.Append(c);
}
if (sb.Length == 0)
return "_";
return changed ? sb.ToString() : text;
}
Oto wersja zaakceptowanej odpowiedzi, Linq
która używa Enumerable.Aggregate
:
string fileName = "something";
Path.GetInvalidFileNameChars()
.Aggregate(fileName, (current, c) => current.Replace(c, '_'));
Diego ma właściwe rozwiązanie, ale jest tam jeden bardzo mały błąd. Używana wersja string.Replace powinna być string.Replace (char, char), nie ma łańcucha.Replace (char, string)
Nie mogę edytować odpowiedzi lub właśnie wprowadziłbym drobną zmianę.
Więc powinno być:
string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
Oto drobny zwrot w odpowiedzi Diego.
Jeśli nie boisz się Unicode, możesz zachować nieco większą wierność, zastępując nieprawidłowe znaki prawidłowymi symbolami Unicode, które je przypominają. Oto kod, którego użyłem w niedawnym projekcie dotyczącym list krojenia drewna:
static string MakeValidFilename(string text) {
text = text.Replace('\'', '’'); // U+2019 right single quotation mark
text = text.Replace('"', '”'); // U+201D right double quotation mark
text = text.Replace('/', '⁄'); // U+2044 fraction slash
foreach (char c in System.IO.Path.GetInvalidFileNameChars()) {
text = text.Replace(c, '_');
}
return text;
}
Tworzy nazwy plików takie jak 1⁄2” spruce.txt
zamiast1_2_ spruce.txt
Tak, to naprawdę działa:
Caveat Emptor
Wiedziałem, że ta sztuczka zadziała na NTFS, ale byłem zaskoczony, że działa również na partycjach FAT i FAT32. To dlatego, że długie nazwy plików są przechowywane w Unicode , nawet tak daleko wstecz jak Windows 95 / NT. Testowałem na Win7, XP, a nawet na routerze opartym na Linuksie i pokazały się OK. Nie mogę powiedzieć tego samego o wnętrzu DOSBox.
To powiedziawszy, zanim zwariujesz z tym, zastanów się, czy naprawdę potrzebujesz dodatkowej wierności. Podobieństwa do Unicode mogą zmylić ludzi lub stare programy, np. Starsze systemy operacyjne polegające na stronach kodowych .
Oto wersja, która używa StringBuilder
i IndexOfAny
z dołączaniem zbiorczym dla pełnej wydajności. Zwraca również oryginalny ciąg zamiast tworzyć zduplikowany ciąg.
Wreszcie, zawiera instrukcję przełącznika, która zwraca wyglądające znaki, które można dostosować w dowolny sposób. Zapoznaj się z wyszukiwaniem elementów zagmatwanych na Unicode.org, aby zobaczyć, jakie opcje mogą być dostępne w zależności od czcionki.
public static string GetSafeFilename(string arbitraryString)
{
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
var replaceIndex = arbitraryString.IndexOfAny(invalidChars, 0);
if (replaceIndex == -1) return arbitraryString;
var r = new StringBuilder();
var i = 0;
do
{
r.Append(arbitraryString, i, replaceIndex - i);
switch (arbitraryString[replaceIndex])
{
case '"':
r.Append("''");
break;
case '<':
r.Append('\u02c2'); // '˂' (modifier letter left arrowhead)
break;
case '>':
r.Append('\u02c3'); // '˃' (modifier letter right arrowhead)
break;
case '|':
r.Append('\u2223'); // '∣' (divides)
break;
case ':':
r.Append('-');
break;
case '*':
r.Append('\u2217'); // '∗' (asterisk operator)
break;
case '\\':
case '/':
r.Append('\u2044'); // '⁄' (fraction slash)
break;
case '\0':
case '\f':
case '?':
break;
case '\t':
case '\n':
case '\r':
case '\v':
r.Append(' ');
break;
default:
r.Append('_');
break;
}
i = replaceIndex + 1;
replaceIndex = arbitraryString.IndexOfAny(invalidChars, i);
} while (replaceIndex != -1);
r.Append(arbitraryString, i, arbitraryString.Length - i);
return r.ToString();
}
To nie sprawdza .
, ..
czy zarezerwowane nazwy takie jak CON
, ponieważ nie jest jasne, co powinno być zastąpienie.
Trochę wyczyszczę mój kod i zrobię trochę refaktoryzacji ... Stworzyłem rozszerzenie dla typu string:
public static string ToValidFileName(this string s, char replaceChar = '_', char[] includeChars = null)
{
var invalid = Path.GetInvalidFileNameChars();
if (includeChars != null) invalid = invalid.Union(includeChars).ToArray();
return string.Join(string.Empty, s.ToCharArray().Select(o => o.In(invalid) ? replaceChar : o));
}
Teraz jest łatwiejszy w użyciu z:
var name = "Any string you want using ? / \ or even +.zip";
var validFileName = name.ToValidFileName();
Jeśli chcesz zamienić na inny znak niż „_”, możesz użyć:
var validFileName = name.ToValidFileName(replaceChar:'#');
I możesz dodać znaki, aby zastąpić ... na przykład nie chcesz spacji ani przecinków:
var validFileName = name.ToValidFileName(includeChars: new [] { ' ', ',' });
Mam nadzieję, że to pomoże...
Twoje zdrowie
Kolejne proste rozwiązanie:
private string MakeValidFileName(string original, char replacementChar = '_')
{
var invalidChars = new HashSet<char>(Path.GetInvalidFileNameChars());
return new string(original.Select(c => invalidChars.Contains(c) ? replacementChar : c).ToArray());
}
Prosty kod jednowierszowy:
var validFileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Możesz zawinąć go w metodę rozszerzenia, jeśli chcesz go ponownie użyć.
public static string ToValidFileName(this string fileName) => Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Potrzebowałem systemu, który nie mógł tworzyć kolizji, więc nie mogłem odwzorować wielu znaków na jeden. Skończyło się na:
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)));
}
}
Musiałem to zrobić dzisiaj ... w moim przypadku musiałem połączyć nazwę klienta z datą i godziną dla końcowego pliku .kmz. Moje ostateczne rozwiązanie było takie:
string name = "Whatever name with valid/invalid chars";
char[] invalid = System.IO.Path.GetInvalidFileNameChars();
string validFileName = string.Join(string.Empty,
string.Format("{0}.{1:G}.kmz", name, DateTime.Now)
.ToCharArray().Select(o => o.In(invalid) ? '_' : o));
Możesz nawet zastąpić spacje, jeśli dodasz znak spacji do nieprawidłowej tablicy.
Może nie jest najszybszy, ale ponieważ wydajność nie była problemem, uznałem to za eleganckie i zrozumiałe.
Twoje zdrowie!
Możesz to zrobić za pomocą sed
polecenia:
sed -e "
s/[?()\[\]=+<>:;©®”,*|]/_/g
s/"$'\t'"/ /g
s/–/-/g
s/\"/_/g
s/[[:cntrl:]]/_/g"