Comment créer un nom de fichier Windows valide à partir d'une chaîne arbitraire?
97
J'ai une chaîne comme "Foo: Bar" que je veux utiliser comme nom de fichier, mais sous Windows, le caractère ":" n'est pas autorisé dans un nom de fichier.
Existe-t-il une méthode qui transforme "Foo: Bar" en quelque chose comme "Foo-Bar"?
J'ai fait la même chose aujourd'hui. Je n'ai pas vérifié SO pour une raison quelconque, mais j'ai quand même trouvé la réponse.
Aaron Smith
Réponses:
153
Essayez quelque chose comme ceci:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Éditer:
Comme GetInvalidFileNameChars()retournera 10 ou 15 caractères, il est préférable d'utiliser a StringBuilderau lieu d'une simple chaîne; la version originale prendra plus de temps et consommera plus de mémoire.
Vous pouvez utiliser un StringBuilder si vous le souhaitez, mais si les noms sont courts et je suppose que cela ne vaut pas la peine. Vous pouvez également créer votre propre méthode pour créer un char [] et remplacer tous les mauvais caractères en une seule itération. Il vaut toujours mieux garder les choses simples à moins que cela ne fonctionne pas, vous pourriez avoir des
La probabilité d'avoir plus de 2 caractères invalides différents dans la chaîne est si faible que se soucier des performances de string.Replace () est inutile.
Serge Wautier
1
Excellente solution, intéressante mise à part, resharper a suggéré cette version Linq: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); Je me demande s'il y a des améliorations de performances possibles. J'ai conservé l'original à des fins de lisibilité car les performances ne sont pas ma plus grande préoccupation. Mais si quelqu'un est intéressé, cela pourrait valoir la peine d'être comparé
chrispepper1989
1
@AndyM Pas besoin de le faire. file.name.txt.pdfest un pdf valide. Windows ne lit que le dernier .pour l'extension.
Diego Jancic
33
fileName = fileName.Replace(":","-")
Cependant ":" n'est pas le seul caractère illégal pour Windows. Vous devrez également gérer:
/, \, :,*,?,", <, > and |
Ceux-ci sont contenus dans System.IO.Path.GetInvalidFileNameChars ();
Aussi (sous Windows), "." ne peut pas être le seul caractère du nom de fichier (les deux ".", "..", "...", etc. sont invalides). Soyez prudent lorsque vous nommez des fichiers avec ".", Par exemple:
echo "test">.test.
Générera un fichier nommé ".test"
Enfin, si vous voulez vraiment faire les choses correctement, vous devez rechercher certains noms de fichiers spéciaux . Sous Windows, vous ne pouvez pas créer de fichiers nommés:
Je n'ai jamais connu les noms réservés. Cela a du sens
Greg Dean
4
De plus, pour ce que ça vaut, vous ne pouvez pas créer un nom de fichier commençant par l'un de ces noms réservés, suivi d'une décimale. ie con.air.avi
John Conrad
".foo" est un nom de fichier valide. Je ne connaissais pas le nom de fichier "CON" - à quoi ça sert?
configurateur
Grattez ça. CON est pour la console.
configurateur
Merci configurateur; J'ai mis à jour la réponse, vous avez raison ".foo" est valide; cependant ".foo." conduit à des résultats possibles et indésirables. Actualisé.
Phil Price
13
Ce n'est pas plus efficace, mais c'est plus amusant :)
var fileName ="foo:bar";var invalidChars =System.IO.Path.GetInvalidFileNameChars();var cleanFileName =newstring(fileName.Where(m =>!invalidChars.Contains(m)).ToArray<char>());
Si quelqu'un veut une version optimisée basée sur StringBuilder, utilisez ceci. Inclut le truc de rkagerer en option.
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 pour un code agréable et lisible. Rend très facile à lire et à remarquer les bogues: P .. Cette fonction doit toujours renvoyer la chaîne d'origine car modifiée ne sera jamais vraie.
Erti-Chris Eelmaa
Merci, je pense que c'est mieux maintenant. Vous savez ce qu'ils disent à propos de l'open source, "beaucoup d'yeux rendent tous les bugs superficiels donc je n'ai pas à écrire de tests unitaires" ...
Qwertie
8
Voici une version de la réponse acceptée en utilisant Linqqui utilise Enumerable.Aggregate:
Diego a la bonne solution, mais il y a une très petite erreur là-dedans. La version de string.Replace utilisée doit être string.Replace (char, char), il n'y a pas de string.Replace (char, string)
Je ne peux pas modifier la réponse ou j'aurais juste fait le changement mineur.
Donc ça devrait être:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Si vous n'avez pas peur d'Unicode, vous pouvez conserver un peu plus de fidélité en remplaçant les caractères non valides par des symboles Unicode valides qui leur ressemblent. Voici le code que j'ai utilisé dans un projet récent impliquant des listes de coupe de bois:
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;}
Cela produit des noms de fichiers comme 1⁄2” spruce.txtau lieu de1_2_ spruce.txt
Oui, ça marche vraiment:
Caveat Emptor
Je savais que cette astuce fonctionnerait sur NTFS mais j'ai été surpris de constater qu'elle fonctionne également sur les partitions FAT et FAT32. C'est parce que les noms de fichiers longs sont stockés en Unicode , même aussi loin que Windows 95 / NT. J'ai testé sur Win7, XP et même un routeur basé sur Linux et ils se sont montrés OK. Je ne peux pas dire la même chose à l'intérieur d'une DOSBox.
Cela dit, avant de devenir fou avec cela, demandez-vous si vous avez vraiment besoin de la fidélité supplémentaire. Les sosies Unicode pourraient semer la confusion chez les personnes ou les anciens programmes, par exemple les anciens OS reposant sur des pages de codes .
Voici une version qui utilise StringBuilderet IndexOfAnyavec un ajout en masse pour une efficacité totale. Il renvoie également la chaîne d'origine plutôt que de créer une chaîne en double.
Dernier point mais non le moindre, il dispose d'une instruction switch qui renvoie des caractères similaires que vous pouvez personnaliser comme vous le souhaitez. Consultez la recherche confusables d'Unicode.org pour voir quelles options vous pourriez avoir, en fonction de la police.
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();}
Il ne vérifie pas ., ..ou des noms réservés comme CONparce qu'il est pas clair que le remplacement doit être.
J'avais besoin d'un système qui ne pouvait pas créer de collisions, donc je ne pouvais pas mapper plusieurs personnages sur un seul. J'ai fini avec:
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)));}}
J'avais besoin de le faire aujourd'hui ... dans mon cas, j'avais besoin de concaténer un nom de client avec la date et l'heure pour un fichier .kmz final. Ma solution finale était la suivante:
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));
Vous pouvez même le faire remplacer des espaces si vous ajoutez le caractère d'espace au tableau invalide.
Ce n'est peut-être pas le plus rapide, mais comme les performances n'étaient pas un problème, je l'ai trouvé élégant et compréhensible.
Réponses:
Essayez quelque chose comme ceci:
Éditer:
Comme
GetInvalidFileNameChars()
retournera 10 ou 15 caractères, il est préférable d'utiliser aStringBuilder
au lieu d'une simple chaîne; la version originale prendra plus de temps et consommera plus de mémoire.la source
file.name.txt.pdf
est un pdf valide. Windows ne lit que le dernier.
pour l'extension.Cependant ":" n'est pas le seul caractère illégal pour Windows. Vous devrez également gérer:
Ceux-ci sont contenus dans System.IO.Path.GetInvalidFileNameChars ();
Aussi (sous Windows), "." ne peut pas être le seul caractère du nom de fichier (les deux ".", "..", "...", etc. sont invalides). Soyez prudent lorsque vous nommez des fichiers avec ".", Par exemple:
Générera un fichier nommé ".test"
Enfin, si vous voulez vraiment faire les choses correctement, vous devez rechercher certains noms de fichiers spéciaux . Sous Windows, vous ne pouvez pas créer de fichiers nommés:
la source
Ce n'est pas plus efficace, mais c'est plus amusant :)
la source
Si quelqu'un veut une version optimisée basée sur
StringBuilder
, utilisez ceci. Inclut le truc de rkagerer en option.la source
Voici une version de la réponse acceptée en utilisant
Linq
qui utiliseEnumerable.Aggregate
:la source
Diego a la bonne solution, mais il y a une très petite erreur là-dedans. La version de string.Replace utilisée doit être string.Replace (char, char), il n'y a pas de string.Replace (char, string)
Je ne peux pas modifier la réponse ou j'aurais juste fait le changement mineur.
Donc ça devrait être:
la source
Voici une légère torsion de la réponse de Diego.
Si vous n'avez pas peur d'Unicode, vous pouvez conserver un peu plus de fidélité en remplaçant les caractères non valides par des symboles Unicode valides qui leur ressemblent. Voici le code que j'ai utilisé dans un projet récent impliquant des listes de coupe de bois:
Cela produit des noms de fichiers comme
1⁄2” spruce.txt
au lieu de1_2_ spruce.txt
Oui, ça marche vraiment:
Caveat Emptor
Je savais que cette astuce fonctionnerait sur NTFS mais j'ai été surpris de constater qu'elle fonctionne également sur les partitions FAT et FAT32. C'est parce que les noms de fichiers longs sont stockés en Unicode , même aussi loin que Windows 95 / NT. J'ai testé sur Win7, XP et même un routeur basé sur Linux et ils se sont montrés OK. Je ne peux pas dire la même chose à l'intérieur d'une DOSBox.
Cela dit, avant de devenir fou avec cela, demandez-vous si vous avez vraiment besoin de la fidélité supplémentaire. Les sosies Unicode pourraient semer la confusion chez les personnes ou les anciens programmes, par exemple les anciens OS reposant sur des pages de codes .
la source
Voici une version qui utilise
StringBuilder
etIndexOfAny
avec un ajout en masse pour une efficacité totale. Il renvoie également la chaîne d'origine plutôt que de créer une chaîne en double.Dernier point mais non le moindre, il dispose d'une instruction switch qui renvoie des caractères similaires que vous pouvez personnaliser comme vous le souhaitez. Consultez la recherche confusables d'Unicode.org pour voir quelles options vous pourriez avoir, en fonction de la police.
Il ne vérifie pas
.
,..
ou des noms réservés commeCON
parce qu'il est pas clair que le remplacement doit être.la source
Nettoyer un peu mon code et faire un peu de refactoring ... J'ai créé une extension pour le type string:
Maintenant, c'est plus facile à utiliser avec:
Si vous souhaitez remplacer par un caractère différent de "_", vous pouvez utiliser:
Et vous pouvez ajouter des caractères à remplacer .. par exemple, vous ne voulez pas d'espaces ni de virgules:
J'espère que ça aide...
À votre santé
la source
Une autre solution simple:
la source
Un simple code en une ligne:
Vous pouvez l'envelopper dans une méthode d'extension si vous souhaitez le réutiliser.
la source
J'avais besoin d'un système qui ne pouvait pas créer de collisions, donc je ne pouvais pas mapper plusieurs personnages sur un seul. J'ai fini avec:
la source
J'avais besoin de le faire aujourd'hui ... dans mon cas, j'avais besoin de concaténer un nom de client avec la date et l'heure pour un fichier .kmz final. Ma solution finale était la suivante:
Vous pouvez même le faire remplacer des espaces si vous ajoutez le caractère d'espace au tableau invalide.
Ce n'est peut-être pas le plus rapide, mais comme les performances n'étaient pas un problème, je l'ai trouvé élégant et compréhensible.
À votre santé!
la source
Vous pouvez le faire avec une
sed
commande:la source