Ho una stringa come "Foo: Bar" che voglio usare come nome di file, ma su Windows il carattere ":" non è consentito in un nome di file.
C'è un metodo che trasformerà "Foo: Bar" in qualcosa come "Foo Bar"?
Ho una stringa come "Foo: Bar" che voglio usare come nome di file, ma su Windows il carattere ":" non è consentito in un nome di file.
C'è un metodo che trasformerà "Foo: Bar" in qualcosa come "Foo Bar"?
Risposte:
Prova qualcosa di simile:
string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
Modificare:
Poiché GetInvalidFileNameChars()
restituirà 10 o 15 caratteri, è meglio usare a StringBuilder
invece di una semplice stringa; la versione originale richiederà più tempo e consumerà più memoria.
file.name.txt.pdf
è un pdf valido. Windows legge solo l'ultimo .
per l'estensione.
fileName = fileName.Replace(":", "-")
Tuttavia ":" non è l'unico carattere illegale per Windows. Dovrai anche gestire:
/, \, :, *, ?, ", <, > and |
Questi sono contenuti in System.IO.Path.GetInvalidFileNameChars ();
Inoltre (su Windows), "." non può essere l'unico carattere nel nome del file (entrambi ".", "..", "..." e così via non sono validi). Fare attenzione quando si denominano i file con ".", Ad esempio:
echo "test" > .test.
Genererà un file denominato ".test"
Infine, se vuoi davvero fare le cose correttamente, ci sono alcuni nomi di file speciali che devi cercare. Su Windows non puoi creare file denominati:
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.
Non è più efficiente, ma è più divertente :)
var fileName = "foo:bar";
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
var cleanFileName = new string(fileName.Where(m => !invalidChars.Contains(m)).ToArray<char>());
Nel caso in cui qualcuno desideri una versione ottimizzata basata su StringBuilder
, usa questo. Include il trucco di rkagerer come opzione.
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;
}
Ecco una versione della risposta accettata utilizzando Linq
quale utilizza Enumerable.Aggregate
:
string fileName = "something";
Path.GetInvalidFileNameChars()
.Aggregate(fileName, (current, c) => current.Replace(c, '_'));
Diego ha la soluzione corretta ma c'è un piccolo errore lì dentro. La versione di string.Replace utilizzata dovrebbe essere string.Replace (char, char), non c'è una stringa.Replace (char, string)
Non riesco a modificare la risposta o avrei solo apportato una piccola modifica.
Quindi dovrebbe essere:
string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
Ecco una leggera svolta nella risposta di Diego.
Se non hai paura di Unicode, puoi mantenere un po 'più di fedeltà sostituendo i caratteri non validi con simboli Unicode validi che li assomigliano. Ecco il codice che ho usato in un recente progetto che coinvolgeva cutlist di legname:
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;
}
Questo produce nomi di file come 1⁄2” spruce.txt
invece di1_2_ spruce.txt
Sì, funziona davvero:
Caveat Emptor
Sapevo che questo trucco avrebbe funzionato su NTFS, ma sono rimasto sorpreso di scoprire che funziona anche su partizioni FAT e FAT32. Questo perché i nomi di file lunghi vengono memorizzati in Unicode , anche nel passato a Windows 95 / NT. Ho provato su Win7, XP e persino un router basato su Linux e sono risultati OK. Non posso dire lo stesso per all'interno di un DOSBox.
Detto questo, prima di impazzire con questo, considera se hai davvero bisogno della fedeltà extra. I sosia di Unicode potrebbero confondere le persone oi vecchi programmi, ad esempio i vecchi sistemi operativi che si affidano alle tabelle codici .
Ecco una versione che utilizza StringBuilder
eIndexOfAny
con aggiunta di massa per la piena efficienza. Restituisce anche la stringa originale anziché creare una stringa duplicata.
Ultimo ma non meno importante, ha un'istruzione switch che restituisce caratteri simili che puoi personalizzare in qualsiasi modo desideri. Controlla la ricerca di elementi confusibili di Unicode.org per vedere quali opzioni potresti avere, a seconda del carattere.
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();
}
Esso non controlla .
, ..
o nomi riservati come CON
, perché non è chiaro quale sia la sostituzione dovrebbe essere.
Pulendo un po 'il mio codice e facendo un po' di refactoring ... ho creato un'estensione per il tipo di stringa:
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));
}
Ora è più facile da usare con:
var name = "Any string you want using ? / \ or even +.zip";
var validFileName = name.ToValidFileName();
Se vuoi sostituire con un carattere diverso da "_" puoi usare:
var validFileName = name.ToValidFileName(replaceChar:'#');
E puoi aggiungere caratteri da sostituire .. per esempio non vuoi spazi o virgole:
var validFileName = name.ToValidFileName(includeChars: new [] { ' ', ',' });
Spero che sia d'aiuto...
Saluti
Un'altra semplice soluzione:
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 semplice codice di una riga:
var validFileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Puoi avvolgerlo in un metodo di estensione se desideri riutilizzarlo.
public static string ToValidFileName(this string fileName) => Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Avevo bisogno di un sistema che non potesse creare collisioni, quindi non potevo mappare più personaggi su uno. Ho finito con:
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)));
}
}
Avevo bisogno di farlo oggi ... nel mio caso, avevo bisogno di concatenare il nome di un cliente con la data e l'ora per un file .kmz finale. La mia soluzione finale è stata questa:
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));
Puoi anche fare in modo che sostituisca gli spazi se aggiungi lo spazio char all'array non valido.
Forse non è il più veloce, ma poiché le prestazioni non erano un problema, l'ho trovato elegante e comprensibile.
Saluti!
Puoi farlo con un sed
comando:
sed -e "
s/[?()\[\]=+<>:;©®”,*|]/_/g
s/"$'\t'"/ /g
s/–/-/g
s/\"/_/g
s/[[:cntrl:]]/_/g"