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 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"?
Réponses:
Essayez quelque chose comme ceci:
string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
Éditer:
Comme GetInvalidFileNameChars()
retournera 10 ou 15 caractères, il est préférable d'utiliser a StringBuilder
au lieu d'une simple chaîne; la version originale prendra plus de temps et consommera plus de mémoire.
file.name.txt.pdf
est un pdf valide. Windows ne lit que le dernier .
pour l'extension.
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:
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.
Ce n'est pas plus efficace, mais c'est plus amusant :)
var fileName = "foo:bar";
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
var cleanFileName = new string(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.
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;
}
Voici une version de la réponse acceptée en utilisant Linq
qui utilise Enumerable.Aggregate
:
string fileName = "something";
Path.GetInvalidFileNameChars()
.Aggregate(fileName, (current, c) => current.Replace(c, '_'));
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 in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
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:
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;
}
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 .
Voici une version qui utilise StringBuilder
et IndexOfAny
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.
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();
}
Il ne vérifie pas .
, ..
ou des noms réservés comme CON
parce qu'il est pas clair que le remplacement doit être.
Nettoyer un peu mon code et faire un peu de refactoring ... J'ai créé une extension pour le type 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));
}
Maintenant, c'est plus facile à utiliser avec:
var name = "Any string you want using ? / \ or even +.zip";
var validFileName = name.ToValidFileName();
Si vous souhaitez remplacer par un caractère différent de "_", vous pouvez utiliser:
var validFileName = name.ToValidFileName(replaceChar:'#');
Et vous pouvez ajouter des caractères à remplacer .. par exemple, vous ne voulez pas d'espaces ni de virgules:
var validFileName = name.ToValidFileName(includeChars: new [] { ' ', ',' });
J'espère que ça aide...
À votre santé
Une autre solution simple:
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());
}
Un simple code en une ligne:
var validFileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Vous pouvez l'envelopper dans une méthode d'extension si vous souhaitez le réutiliser.
public static string ToValidFileName(this string fileName) => Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
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:
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)));
}
}
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.
À votre santé!
Vous pouvez le faire avec une sed
commande:
sed -e "
s/[?()\[\]=+<>:;©®”,*|]/_/g
s/"$'\t'"/ /g
s/–/-/g
s/\"/_/g
s/[[:cntrl:]]/_/g"