Я сделал то же самое сегодня. Я почему-то не проверял ТАК, но все равно нашел ответ.
Аарон Смит,
Ответы:
154
Попробуйте что-то вроде этого:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Редактировать:
Так GetInvalidFileNameChars()как вернет 10 или 15 символов, лучше использовать StringBuilderвместо простой строки; исходная версия займет больше времени и потребляет больше памяти.
Вы можете использовать StringBuilder, если хотите, но если имена короткие, и я думаю, это того не стоит. Вы также можете создать свой собственный метод для создания char [] и замены всех неправильных символов за одну итерацию. Всегда лучше делать это простым, если это не работает, у вас могут быть более узкие горлышки
Вероятность иметь 2+ разных недопустимых символа в строке настолько мала, что заботиться о производительности string.Replace () бессмысленно.
Серж Вотье,
1
Отличное решение, помимо интересного, resharper предложил эту версию Linq: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); Интересно, есть ли там возможные улучшения производительности. Я сохранил оригинал для удобства чтения, так как производительность не является моей самой большой проблемой. Но если кому-то интересно, может быть, стоит провести сравнительный анализ
chrispepper1989,
1
@AndyM В этом нет необходимости. file.name.txt.pdfэто действительный PDF-файл. Windows читает только последнее .для расширения.
Диего Янчич
33
fileName = fileName.Replace(":","-")
Однако «:» - не единственный недопустимый символ для Windows. Вам также придется обрабатывать:
/, \, :,*,?,", <, > and |
Они содержатся в System.IO.Path.GetInvalidFileNameChars ();
Также (в Windows) "." не может быть единственным символом в имени файла (оба символа «.», «..», «...» и т. д. недопустимы). Будьте осторожны, называя файлы с помощью ".", Например:
echo "test">.test.
Сгенерирует файл с именем ".test"
Наконец, если вы действительно хотите делать что-то правильно, есть несколько специальных имен файлов, на которые нужно обратить внимание. В Windows вы не можете создавать файлы с именами:
Я никогда не знал о зарезервированных именах. Хотя это имеет смысл
Грег Дин
4
Кроме того, как бы то ни было, вы не можете создать имя файла, начинающееся с одного из этих зарезервированных имен, за которым следует десятичная дробь. т.е. con.air.avi
Джон Конрад
".foo" - допустимое имя файла. Не знал о имени файла "CON" - для чего оно?
конфигуратор
Сотрите это. CON для консоли.
конфигуратор
Спасибо конфигуратору; Я обновил ответ, вы правы. ".Foo" действительно; однако ".foo." приводит к возможным нежелательным результатам. Обновлено.
Фил Прайс,
13
Это не эффективнее, но веселее :)
var fileName ="foo:bar";var invalidChars =System.IO.Path.GetInvalidFileNameChars();var cleanFileName =newstring(fileName.Where(m =>!invalidChars.Contains(m)).ToArray<char>());
Если кому-то нужна оптимизированная версия на основе StringBuilder, используйте это. В качестве опции включает трюк rkagerer .
staticchar[] _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>publicstaticstringMakeValidFileName(string text,char? replacement ='_',bool fancy =true){StringBuilder sb =newStringBuilder(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 markelseif(c =='\'') repl ='’';// U+2019 right single quotation markelseif(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;}
+1 за красивый и читаемый код. Облегчает чтение и обнаружение ошибок: P .. Эта функция всегда должна возвращать исходную строку, поскольку измененная строка никогда не будет истинной.
Эрти-Крис Элмаа
Спасибо, думаю, сейчас лучше. Вы знаете, что они говорят об открытом исходном коде: «Многие глаза делают все ошибки поверхностными, поэтому мне не нужно писать модульные тесты» ...
Qwertie
8
Вот небольшой поворот в ответе Диего.
Если вы не боитесь Unicode, вы можете сохранить немного большую точность, заменив недопустимые символы на действительные символы Unicode, которые на них похожи. Вот код, который я использовал в недавнем проекте по спискам срезов пиломатериалов:
staticstringMakeValidFilename(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 slashforeach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
text = text.Replace(c,'_');}return text;}
Это создает имена файлов, например, 1⁄2” spruce.txtвместо1_2_ spruce.txt
Да, действительно работает:
Пусть покупатель будет бдителен
Я знал, что этот трюк будет работать с NTFS, но был удивлен, обнаружив, что он также работает с разделами FAT и FAT32. Это потому , что длинные имена файлов будут сохранены в Unicode , даже еще в Windows 95 / NT. Я тестировал Win7, XP и даже маршрутизатор на базе Linux, и они показали себя нормально. Не могу сказать то же самое для DOSBox.
Тем не менее, прежде чем вы сходите с ума от этого, подумайте, действительно ли вам нужна дополнительная точность. Сходства Unicode могут сбивать с толку людей или старые программы, например, старые ОС, полагающиеся на кодовые страницы .
У Диего действительно есть правильное решение, но есть одна очень маленькая ошибка. Используемая версия string.Replace должна быть string.Replace (char, char), строки нет.Replace (char, string)
Я не могу отредактировать ответ, иначе я бы просто внес незначительное изменение.
Так и должно быть:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Вот версия, которая использует StringBuilderи IndexOfAnyс массовым добавлением для полной эффективности. Он также возвращает исходную строку, а не создает повторяющуюся строку.
И последнее, но не менее важное: в нем есть оператор switch, который возвращает похожие символы, которые вы можете настроить по своему усмотрению. Ознакомьтесь с поиском confusables на Unicode.org, чтобы узнать, какие варианты у вас могут быть в зависимости от шрифта.
publicstaticstringGetSafeFilename(string arbitraryString){var invalidChars =System.IO.Path.GetInvalidFileNameChars();var replaceIndex = arbitraryString.IndexOfAny(invalidChars,0);if(replaceIndex ==-1)return arbitraryString;var r =newStringBuilder();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();}
Он не проверяет ., ..или зарезервированные имена , как , CONпотому что не ясно , что замена должна быть.
Мне нужна была система, которая не могла создавать коллизии, чтобы я не мог сопоставить несколько символов одному. Я получил:
publicstaticclassExtension{/// <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>privatestaticreadonlyHashSet<char>CleanFileNameChars=newHashSet<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>publicstaticstringCleanFileName(thisstring dirtyFileName){stringEscapeHexString(char c)=>"{"+(c >255? $"{(uint)c:X4}": $"{(uint)c:X2}")+"}";returnstring.Join(string.Empty,
dirtyFileName.Select(
c =>
c =='{'?"{{":
c =='}'?"}}":CleanFileNameChars.Contains(c)? $"{c}":EscapeHexString(c)));}}
Мне нужно было сделать это сегодня ... в моем случае мне нужно было объединить имя клиента с датой и временем для окончательного файла .kmz. Мое окончательное решение было таким:
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));
Вы даже можете заставить его заменить пробелы, если вы добавите пробел в неверный массив.
Возможно, он не самый быстрый, но, поскольку производительность не была проблемой, я нашел его элегантным и понятным.
Ответы:
Попробуйте что-то вроде этого:
Редактировать:
Так
GetInvalidFileNameChars()
как вернет 10 или 15 символов, лучше использоватьStringBuilder
вместо простой строки; исходная версия займет больше времени и потребляет больше памяти.источник
file.name.txt.pdf
это действительный PDF-файл. Windows читает только последнее.
для расширения.Однако «:» - не единственный недопустимый символ для Windows. Вам также придется обрабатывать:
Они содержатся в System.IO.Path.GetInvalidFileNameChars ();
Также (в Windows) "." не может быть единственным символом в имени файла (оба символа «.», «..», «...» и т. д. недопустимы). Будьте осторожны, называя файлы с помощью ".", Например:
Сгенерирует файл с именем ".test"
Наконец, если вы действительно хотите делать что-то правильно, есть несколько специальных имен файлов, на которые нужно обратить внимание. В Windows вы не можете создавать файлы с именами:
источник
Это не эффективнее, но веселее :)
источник
Если кому-то нужна оптимизированная версия на основе
StringBuilder
, используйте это. В качестве опции включает трюк rkagerer .источник
Вот небольшой поворот в ответе Диего.
Если вы не боитесь Unicode, вы можете сохранить немного большую точность, заменив недопустимые символы на действительные символы Unicode, которые на них похожи. Вот код, который я использовал в недавнем проекте по спискам срезов пиломатериалов:
Это создает имена файлов, например,
1⁄2” spruce.txt
вместо1_2_ spruce.txt
Да, действительно работает:
Пусть покупатель будет бдителен
Я знал, что этот трюк будет работать с NTFS, но был удивлен, обнаружив, что он также работает с разделами FAT и FAT32. Это потому , что длинные имена файлов будут сохранены в Unicode , даже еще в Windows 95 / NT. Я тестировал Win7, XP и даже маршрутизатор на базе Linux, и они показали себя нормально. Не могу сказать то же самое для DOSBox.
Тем не менее, прежде чем вы сходите с ума от этого, подумайте, действительно ли вам нужна дополнительная точность. Сходства Unicode могут сбивать с толку людей или старые программы, например, старые ОС, полагающиеся на кодовые страницы .
источник
Вот версия принятого ответа, в
Linq
которой используютсяEnumerable.Aggregate
:источник
У Диего действительно есть правильное решение, но есть одна очень маленькая ошибка. Используемая версия string.Replace должна быть string.Replace (char, char), строки нет.Replace (char, string)
Я не могу отредактировать ответ, иначе я бы просто внес незначительное изменение.
Так и должно быть:
источник
Вот версия, которая использует
StringBuilder
иIndexOfAny
с массовым добавлением для полной эффективности. Он также возвращает исходную строку, а не создает повторяющуюся строку.И последнее, но не менее важное: в нем есть оператор switch, который возвращает похожие символы, которые вы можете настроить по своему усмотрению. Ознакомьтесь с поиском confusables на Unicode.org, чтобы узнать, какие варианты у вас могут быть в зависимости от шрифта.
Он не проверяет
.
,..
или зарезервированные имена , как ,CON
потому что не ясно , что замена должна быть.источник
Немного почистив свой код и сделав небольшой рефакторинг ... Я создал расширение для строкового типа:
Теперь стало проще использовать:
Если вы хотите заменить символом, отличным от "_", вы можете использовать:
И вы можете добавить символы для замены .. например, вам не нужны пробелы или запятые:
Надеюсь, поможет...
Ура
источник
Еще одно простое решение:
источник
Простой однострочный код:
Вы можете обернуть его в метод расширения, если хотите использовать его повторно.
источник
Мне нужна была система, которая не могла создавать коллизии, чтобы я не мог сопоставить несколько символов одному. Я получил:
источник
Мне нужно было сделать это сегодня ... в моем случае мне нужно было объединить имя клиента с датой и временем для окончательного файла .kmz. Мое окончательное решение было таким:
Вы даже можете заставить его заменить пробелы, если вы добавите пробел в неверный массив.
Возможно, он не самый быстрый, но, поскольку производительность не была проблемой, я нашел его элегантным и понятным.
Ура!
источник
Сделать это можно с помощью
sed
команды:источник