Wie erstelle ich einen gültigen Windows-Dateinamen aus einer beliebigen Zeichenfolge?
97
Ich habe eine Zeichenfolge wie "Foo: Bar", die ich als Dateinamen verwenden möchte, aber unter Windows ist das Zeichen ":" in einem Dateinamen nicht zulässig.
Gibt es eine Methode, die "Foo: Bar" in so etwas wie "Foo-Bar" verwandelt?
Ich habe heute dasselbe gemacht. Ich habe SO aus irgendeinem Grund nicht überprüft, aber trotzdem die Antwort gefunden.
Aaron Smith
Antworten:
153
Versuchen Sie so etwas:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Bearbeiten:
Da GetInvalidFileNameChars()10 oder 15 Zeichen zurückgegeben werden, ist es besser, eine StringBuilderanstelle einer einfachen Zeichenfolge zu verwenden. Die Originalversion dauert länger und verbraucht mehr Speicher.
Sie könnten einen StringBuilder verwenden, wenn Sie möchten, aber wenn die Namen kurz sind und ich denke, es lohnt sich nicht. Sie können auch eine eigene Methode erstellen, um ein Zeichen [] zu erstellen und alle falschen Zeichen in einer Iteration zu ersetzen. Es ist immer besser, es einfach zu halten, es sei denn, es funktioniert nicht. Möglicherweise haben Sie schlechtere
Die Wahrscheinlichkeit, 2+ verschiedene ungültige Zeichen in der Zeichenfolge zu haben, ist so gering, dass es sinnlos ist, sich um die Leistung der Zeichenfolge zu kümmern.
Serge Wautier
1
Gute Lösung, interessant beiseite, resharper schlug diese Linq-Version vor: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); Ich frage mich, ob es dort mögliche Leistungsverbesserungen gibt. Ich habe das Original aus Gründen der Lesbarkeit aufbewahrt, da die Leistung nicht mein größtes Anliegen ist. Aber wenn jemand interessiert ist, könnte es sich lohnen, ein Benchmarking
durchzuführen
1
@AndyM Keine Notwendigkeit. file.name.txt.pdfist ein gültiges PDF. Windows liest nur den letzten .für die Erweiterung.
Diego Jancic
33
fileName = fileName.Replace(":","-")
":" Ist jedoch nicht das einzige unzulässige Zeichen für Windows. Sie müssen auch behandeln:
/, \, :,*,?,", <, > and |
Diese sind in System.IO.Path.GetInvalidFileNameChars () enthalten.
Auch (unter Windows) "." kann nicht das einzige Zeichen im Dateinamen sein (beide ".", "..", "..." usw. sind ungültig). Seien Sie vorsichtig, wenn Sie Dateien mit "." Benennen, zum Beispiel:
echo "test">.test.
Generiert eine Datei mit dem Namen ".test"
Wenn Sie die Dinge wirklich richtig machen möchten, müssen Sie einige spezielle Dateinamen beachten . Unter Windows können Sie keine Dateien mit folgenden Namen erstellen:
Ich wusste nie über die reservierten Namen. Macht aber Sinn
Greg Dean
4
Außerdem können Sie keinen Dateinamen erstellen, der mit einem dieser reservierten Namen beginnt, gefolgt von einer Dezimalstelle. dh con.air.avi
John Conrad
".foo" ist ein gültiger Dateiname. Wussten Sie nicht über den Dateinamen "CON" - wofür ist es?
Konfigurator
Vergiss das. CON ist für die Konsole.
Konfigurator
Danke Konfigurator; Ich habe die Antwort aktualisiert. Sie sind korrekt. ".Foo" ist gültig. jedoch ".foo." führt zu möglichen, unerwünschten Ergebnissen. Aktualisiert.
Phil Price
13
Das ist nicht effizienter, aber es macht mehr Spaß :)
var fileName ="foo:bar";var invalidChars =System.IO.Path.GetInvalidFileNameChars();var cleanFileName =newstring(fileName.Where(m =>!invalidChars.Contains(m)).ToArray<char>());
Wenn jemand eine optimierte Version basierend auf möchte StringBuilder, verwenden Sie diese. Beinhaltet optional den Trick von 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 für schönen und lesbaren Code. Erleichtert das Lesen und Erkennen der Fehler: P .. Diese Funktion sollte immer die ursprüngliche Zeichenfolge zurückgeben, da Änderungen niemals wahr sind.
Erti-Chris Eelmaa
Danke, ich denke es ist jetzt besser. Sie wissen, was sie über Open Source sagen: "Viele Augen machen alle Fehler flach, damit ich keine Unit-Tests schreiben muss" ...
Qwertie
8
Hier ist eine Version der akzeptierten Antwort, Linqdie Folgendes verwendet Enumerable.Aggregate:
Diego hat zwar die richtige Lösung, aber es gibt einen sehr kleinen Fehler. Die verwendete Version von string.Replace sollte string.Replace (char, char) sein, es gibt keinen string.Replace (char, string)
Ich kann die Antwort nicht bearbeiten, sonst hätte ich nur die geringfügige Änderung vorgenommen.
So sollte es sein:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Wenn Sie keine Angst vor Unicode haben, können Sie die Wiedergabetreue verbessern, indem Sie die ungültigen Zeichen durch gültige Unicode-Symbole ersetzen, die ihnen ähneln. Hier ist der Code, den ich kürzlich in einem Projekt mit Schnittholz-Schnittlisten verwendet habe:
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;}
Dies erzeugt Dateinamen wie 1⁄2” spruce.txtanstelle von1_2_ spruce.txt
Ja, es funktioniert wirklich:
Vorbehalt Emptor
Ich wusste, dass dieser Trick unter NTFS funktionieren würde, war aber überrascht, dass er auch auf FAT- und FAT32-Partitionen funktioniert. Dies liegt daran, dass lange Dateinamen in Unicode gespeichert sind , sogar schon unter Windows 95 / NT. Ich habe auf Win7, XP und sogar einem Linux-basierten Router getestet und sie haben sich als OK erwiesen. Kann nicht dasselbe für innerhalb einer DOSBox sagen.
Bevor Sie jedoch verrückt werden, sollten Sie überlegen, ob Sie wirklich die zusätzliche Wiedergabetreue benötigen. Die Unicode-Look-Alikes können Personen oder alte Programme verwirren, z. B. ältere Betriebssysteme, die auf Codepages angewiesen sind .
Hier ist eine Version, die verwendet StringBuilderund IndexOfAnymit Bulk-Append für volle Effizienz. Es wird auch die ursprüngliche Zeichenfolge zurückgegeben, anstatt eine doppelte Zeichenfolge zu erstellen.
Last but not least gibt es eine switch-Anweisung, die ähnliche Zeichen zurückgibt, die Sie nach Belieben anpassen können. Schauen Sie sich die verwirrbare Suche von Unicode.org an, um zu sehen, welche Optionen Sie je nach Schriftart haben könnten.
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();}
Es sucht nicht nach ., ..oder reservierte Namen wie , CONweil es nicht klar ist , was sollte der Ersatz sein.
Ich brauchte ein System, das keine Kollisionen erzeugen konnte, sodass ich nicht mehrere Zeichen einem zuordnen konnte. Am Ende hatte ich:
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)));}}
Ich musste dies heute tun ... in meinem Fall musste ich einen Kundennamen mit dem Datum und der Uhrzeit für eine endgültige .kmz-Datei verketten. Meine endgültige Lösung war folgende:
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));
Sie können sogar Leerzeichen ersetzen, wenn Sie das Leerzeichen char zum ungültigen Array hinzufügen.
Vielleicht ist es nicht das schnellste, aber da Leistung kein Problem war, fand ich es elegant und verständlich.
Antworten:
Versuchen Sie so etwas:
Bearbeiten:
Da
GetInvalidFileNameChars()
10 oder 15 Zeichen zurückgegeben werden, ist es besser, eineStringBuilder
anstelle einer einfachen Zeichenfolge zu verwenden. Die Originalversion dauert länger und verbraucht mehr Speicher.quelle
file.name.txt.pdf
ist ein gültiges PDF. Windows liest nur den letzten.
für die Erweiterung.":" Ist jedoch nicht das einzige unzulässige Zeichen für Windows. Sie müssen auch behandeln:
Diese sind in System.IO.Path.GetInvalidFileNameChars () enthalten.
Auch (unter Windows) "." kann nicht das einzige Zeichen im Dateinamen sein (beide ".", "..", "..." usw. sind ungültig). Seien Sie vorsichtig, wenn Sie Dateien mit "." Benennen, zum Beispiel:
Generiert eine Datei mit dem Namen ".test"
Wenn Sie die Dinge wirklich richtig machen möchten, müssen Sie einige spezielle Dateinamen beachten . Unter Windows können Sie keine Dateien mit folgenden Namen erstellen:
quelle
Das ist nicht effizienter, aber es macht mehr Spaß :)
quelle
Wenn jemand eine optimierte Version basierend auf möchte
StringBuilder
, verwenden Sie diese. Beinhaltet optional den Trick von rkagerer.quelle
Hier ist eine Version der akzeptierten Antwort,
Linq
die Folgendes verwendetEnumerable.Aggregate
:quelle
Diego hat zwar die richtige Lösung, aber es gibt einen sehr kleinen Fehler. Die verwendete Version von string.Replace sollte string.Replace (char, char) sein, es gibt keinen string.Replace (char, string)
Ich kann die Antwort nicht bearbeiten, sonst hätte ich nur die geringfügige Änderung vorgenommen.
So sollte es sein:
quelle
Hier ist eine kleine Wendung zu Diego's Antwort.
Wenn Sie keine Angst vor Unicode haben, können Sie die Wiedergabetreue verbessern, indem Sie die ungültigen Zeichen durch gültige Unicode-Symbole ersetzen, die ihnen ähneln. Hier ist der Code, den ich kürzlich in einem Projekt mit Schnittholz-Schnittlisten verwendet habe:
Dies erzeugt Dateinamen wie
1⁄2” spruce.txt
anstelle von1_2_ spruce.txt
Ja, es funktioniert wirklich:
Vorbehalt Emptor
Ich wusste, dass dieser Trick unter NTFS funktionieren würde, war aber überrascht, dass er auch auf FAT- und FAT32-Partitionen funktioniert. Dies liegt daran, dass lange Dateinamen in Unicode gespeichert sind , sogar schon unter Windows 95 / NT. Ich habe auf Win7, XP und sogar einem Linux-basierten Router getestet und sie haben sich als OK erwiesen. Kann nicht dasselbe für innerhalb einer DOSBox sagen.
Bevor Sie jedoch verrückt werden, sollten Sie überlegen, ob Sie wirklich die zusätzliche Wiedergabetreue benötigen. Die Unicode-Look-Alikes können Personen oder alte Programme verwirren, z. B. ältere Betriebssysteme, die auf Codepages angewiesen sind .
quelle
Hier ist eine Version, die verwendet
StringBuilder
undIndexOfAny
mit Bulk-Append für volle Effizienz. Es wird auch die ursprüngliche Zeichenfolge zurückgegeben, anstatt eine doppelte Zeichenfolge zu erstellen.Last but not least gibt es eine switch-Anweisung, die ähnliche Zeichen zurückgibt, die Sie nach Belieben anpassen können. Schauen Sie sich die verwirrbare Suche von Unicode.org an, um zu sehen, welche Optionen Sie je nach Schriftart haben könnten.
Es sucht nicht nach
.
,..
oder reservierte Namen wie ,CON
weil es nicht klar ist , was sollte der Ersatz sein.quelle
Ein wenig meinen Code bereinigen und ein wenig umgestalten ... Ich habe eine Erweiterung für den String-Typ erstellt:
Jetzt ist es einfacher zu verwenden mit:
Wenn Sie durch ein anderes Zeichen als "_" ersetzen möchten, können Sie Folgendes verwenden:
Und Sie können Zeichen hinzufügen, um sie zu ersetzen. Zum Beispiel möchten Sie keine Leerzeichen oder Kommas:
Ich hoffe es hilft...
Prost
quelle
Eine weitere einfache Lösung:
quelle
Ein einfacher einzeiliger Code:
Sie können es in eine Erweiterungsmethode einschließen, wenn Sie es wiederverwenden möchten.
quelle
Ich brauchte ein System, das keine Kollisionen erzeugen konnte, sodass ich nicht mehrere Zeichen einem zuordnen konnte. Am Ende hatte ich:
quelle
Ich musste dies heute tun ... in meinem Fall musste ich einen Kundennamen mit dem Datum und der Uhrzeit für eine endgültige .kmz-Datei verketten. Meine endgültige Lösung war folgende:
Sie können sogar Leerzeichen ersetzen, wenn Sie das Leerzeichen char zum ungültigen Array hinzufügen.
Vielleicht ist es nicht das schnellste, aber da Leistung kein Problem war, fand ich es elegant und verständlich.
Prost!
quelle
Sie können dies mit einem
sed
Befehl tun :quelle