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?

Ken
quelle
1
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 in System.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.

Diego Jancic
quelle
1
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
Engpässe
2
InvalidFileNameChars = new char [] {'"', '<', '>', '|', '\ 0', '\ x0001', '\ x0002', '\ x0003', '\ x0004', '\ x0005 ',' \ x0006 ',' \ a ',' \ b ',' \ t ',' \ n ',' \ v ',' \ f ',' \ r ',' \ x000e ',' \ x000f ',' \ x0010 ',' \ x0011 ',' \ x0012 ',' \ x0013 ',' \ x0014 ',' \ x0015 ',' \ x0016 ',' \ x0017 ',' \ x0018 ',' \ x0019 ',' \ x001a ',' \ x001b ',' \ x001c ',' \ x001d ',' \ x001e ',' \ x001f ',': ',' * ','? ',' \\ ', '/'};
Diego Jancic
9
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:

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.
Phil Price
quelle
3
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 = new string(fileName.Where(m => !invalidChars.Contains(m)).ToArray<char>());
Joseph Gabriel
quelle
12

Wenn jemand eine optimierte Version basierend auf möchte StringBuilder, verwenden Sie diese. Beinhaltet optional den Trick von rkagerer.

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;
}
Qwertie
quelle
+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:

string fileName = "something";

Path.GetInvalidFileNameChars()
    .Aggregate(fileName, (current, c) => current.Replace(c, '_'));
DavidG
quelle
7

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 in System.IO.Path.GetInvalidFileNameChars())
{
   fileName = fileName.Replace(c, '_');
}
leggetter
quelle
7

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:

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;
}

Dies erzeugt Dateinamen wie 1⁄2” spruce.txtanstelle von1_2_ spruce.txt

Ja, es funktioniert wirklich:

Explorer-Beispiel

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 .

rkagerer
quelle
5

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.

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();
}

Es sucht nicht nach ., ..oder reservierte Namen wie , CONweil es nicht klar ist , was sollte der Ersatz sein.

jnm2
quelle
3

Ein wenig meinen Code bereinigen und ein wenig umgestalten ... Ich habe eine Erweiterung für den String-Typ erstellt:

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));
}

Jetzt ist es einfacher zu verwenden mit:

var name = "Any string you want using ? / \ or even +.zip";
var validFileName = name.ToValidFileName();

Wenn Sie durch ein anderes Zeichen als "_" ersetzen möchten, können Sie Folgendes verwenden:

var validFileName = name.ToValidFileName(replaceChar:'#');

Und Sie können Zeichen hinzufügen, um sie zu ersetzen. Zum Beispiel möchten Sie keine Leerzeichen oder Kommas:

var validFileName = name.ToValidFileName(includeChars: new [] { ' ', ',' });

Ich hoffe es hilft...

Prost

Joan Vilariño
quelle
3

Eine weitere einfache Lösung:

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());
}
GDemartini
quelle
3

Ein einfacher einzeiliger Code:

var validFileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));

Sie können es in eine Erweiterungsmethode einschließen, wenn Sie es wiederverwenden möchten.

public static string ToValidFileName(this string fileName) => Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Moch Yusup
quelle
1

Ich brauchte ein System, das keine Kollisionen erzeugen konnte, sodass ich nicht mehrere Zeichen einem zuordnen konnte. Am Ende hatte ich:

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)));
    }
}
Mheyman
quelle
0

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.

Prost!

Joan Vilariño
quelle
-2

Sie können dies mit einem sedBefehl tun :

 sed -e "
 s/[?()\[\]=+<>:;©®”,*|]/_/g
 s/"$'\t'"/ /g
 s/–/-/g
 s/\"/_/g
 s/[[:cntrl:]]/_/g"
DW
quelle
Eine kompliziertere, aber verwandte Frage finden Sie auch unter: stackoverflow.com/questions/4413427/…
DW
Warum muss dies in C # und nicht in Bash erfolgen? Ich sehe jetzt ein C # -Tag auf der ursprünglichen Frage, aber warum?
DW
1
Ich weiß, richtig, warum nicht einfach von der C # -Anwendung zu Bash wechseln, die möglicherweise nicht installiert ist, um dies zu erreichen?
Peter Ritchie