Zrobiłem to samo dzisiaj. Nie sprawdziłem SO z jakiegoś powodu, ale i tak znalazłem odpowiedź.
Aaron Smith,
Odpowiedzi:
154
Spróbuj czegoś takiego:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Edytować:
Ponieważ GetInvalidFileNameChars()zwróci 10 lub 15 znaków, lepiej jest użyć a StringBuilderzamiast prostego ciągu; oryginalna wersja potrwa dłużej i zużyje więcej pamięci.
Jeśli chcesz, możesz użyć StringBuilder, ale jeśli nazwy są krótkie i myślę, że nie warto. Możesz także stworzyć własną metodę tworzenia znaku char [] i zastępowania wszystkich złych znaków w jednej iteracji. Zawsze lepiej jest zachować prostotę, chyba że to nie działa, możesz mieć gorsze szyjki butelek
Prawdopodobieństwo posiadania ponad 2 różnych nieprawidłowych znaków w ciągu jest tak małe, że dbanie o wydajność string.Replace () jest bezcelowe.
Serge Wautier
1
Świetne rozwiązanie, poza tym ciekawe, resharper zasugerował tę wersję Linq: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); Zastanawiam się, czy są tam możliwe ulepszenia wydajności. Zachowałem oryginał ze względu na czytelność, ponieważ wydajność nie jest moim największym zmartwieniem. Ale jeśli ktoś jest zainteresowany, może warto go
porównać
1
@AndyM Nie ma takiej potrzeby. file.name.txt.pdfjest prawidłowym plikiem PDF. Windows odczytuje tylko ostatnie .rozszerzenie.
Diego Jancic
33
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:
Nigdy nie wiedziałem o zastrzeżonych nazwach. Ma to jednak sens
Greg Dean
4
Poza tym, co jest warte, nie możesz utworzyć nazwy pliku zaczynającej się od jednej z tych zastrzeżonych nazw, po której następuje ułamek dziesiętny. tj. con.air.avi
John Conrad
„.foo” to poprawna nazwa pliku. Nie wiedziałeś o nazwie pliku „CON” - do czego służy?
konfigurator
Podrap to. CON jest dla konsoli.
konfigurator
Dzięki konfiguratorowi; Zaktualizowałem odpowiedź, masz rację „.foo” jest poprawne; jednak „.foo”. prowadzi do możliwych, niepożądanych rezultatów. Zaktualizowano.
Phil Price,
13
To nie jest bardziej wydajne, ale jest fajniejsze :)
var fileName ="foo:bar";var invalidChars =System.IO.Path.GetInvalidFileNameChars();var cleanFileName =newstring(fileName.Where(m =>!invalidChars.Contains(m)).ToArray<char>());
Jeśli ktoś chce mieć zoptymalizowaną wersję StringBuilder, użyj tego. Zawiera sztuczkę rkagerera jako opcję.
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 za ładny i czytelny kod. Sprawia, że jest bardzo łatwy do odczytania i zauważenia błędów: P .. Ta funkcja powinna zawsze zwracać oryginalny ciąg, ponieważ zmieniony nigdy nie będzie prawdziwy.
Erti-Chris Eelmaa
Dzięki, myślę, że teraz jest lepiej. Wiesz, co mówią o open source, „wiele oczu sprawia, że wszystkie błędy są płytkie, więc nie muszę pisać testów jednostkowych”…
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 inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
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:
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;}
Tworzy nazwy plików takie jak 1⁄2” spruce.txtzamiast1_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 StringBuilderi IndexOfAnyz 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.
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();}
To nie sprawdza ., ..czy zarezerwowane nazwy takie jak CON, ponieważ nie jest jasne, co powinno być zastąpienie.
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:
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)));}}
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.
Odpowiedzi:
Spróbuj czegoś takiego:
Edytować:
Ponieważ
GetInvalidFileNameChars()
zwróci 10 lub 15 znaków, lepiej jest użyć aStringBuilder
zamiast prostego ciągu; oryginalna wersja potrwa dłużej i zużyje więcej pamięci.źródło
file.name.txt.pdf
jest prawidłowym plikiem PDF. Windows odczytuje tylko ostatnie.
rozszerzenie.Jednak „:” nie jest jedynym niedozwolonym znakiem w systemie Windows. Będziesz musiał również poradzić sobie z:
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:
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:
źródło
To nie jest bardziej wydajne, ale jest fajniejsze :)
źródło
Jeśli ktoś chce mieć zoptymalizowaną wersję
StringBuilder
, użyj tego. Zawiera sztuczkę rkagerera jako opcję.źródło
Oto wersja zaakceptowanej odpowiedzi,
Linq
która używaEnumerable.Aggregate
:źródło
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ć:
źródło
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:
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 .
źródło
Oto wersja, która używa
StringBuilder
iIndexOfAny
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.
To nie sprawdza
.
,..
czy zarezerwowane nazwy takie jakCON
, ponieważ nie jest jasne, co powinno być zastąpienie.źródło
Trochę wyczyszczę mój kod i zrobię trochę refaktoryzacji ... Stworzyłem rozszerzenie dla typu string:
Teraz jest łatwiejszy w użyciu z:
Jeśli chcesz zamienić na inny znak niż „_”, możesz użyć:
I możesz dodać znaki, aby zastąpić ... na przykład nie chcesz spacji ani przecinków:
Mam nadzieję, że to pomoże...
Twoje zdrowie
źródło
Kolejne proste rozwiązanie:
źródło
Prosty kod jednowierszowy:
Możesz zawinąć go w metodę rozszerzenia, jeśli chcesz go ponownie użyć.
źródło
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:
źródło
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:
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!
źródło
Możesz to zrobić za pomocą
sed
polecenia:źródło