Jak usunąć niedozwolone znaki ze ścieżki i nazw plików?

456

Potrzebuję solidnego i prostego sposobu na usunięcie nielegalnej ścieżki i znaków pliku z prostego ciągu. Użyłem poniższego kodu, ale wydaje się, że nic nie robi, czego mi brakuje?

using System;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string illegal = "\"M<>\"\\a/ry/ h**ad:>> a\\/:*?\"<>| li*tt|le|| la\"mb.?";

            illegal = illegal.Trim(Path.GetInvalidFileNameChars());
            illegal = illegal.Trim(Path.GetInvalidPathChars());

            Console.WriteLine(illegal);
            Console.ReadLine();
        }
    }
}
Gary Willoughby
źródło
1
Trim usuwa znaki z początku i końca łańcucha. Jednak prawdopodobnie powinieneś zapytać, dlaczego dane są nieprawidłowe, i zamiast próbować je dezynfekować / naprawić, odrzuć dane.
user7116
8
Nazwy w stylu uniksowym są niepoprawne w systemie Windows i nie chcę zajmować się skrótami 8.3.
Gary Willoughby
GetInvalidFileNameChars()usunie rzeczy takie jak: \ etc ze ścieżek folderów.
CAD bloke
1
Path.GetInvalidPathChars()wydaje się nie rozbierać *ani?
CAD bloke
18
Przetestowałem pięć odpowiedzi na to pytanie (pętla czasowa 100 000) i następująca metoda jest najszybsza. Wyrażenie regularne zajęło 2 miejsce i było o 25% wolniejsze: ciąg publiczny GetSafeFilename (nazwa pliku ciągu) {return string.Join ("_", filename.Split (Path.GetInvalidFileNameChars ())); }
Brain2000

Odpowiedzi:

494

Zamiast tego spróbuj czegoś takiego;

string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());

foreach (char c in invalid)
{
    illegal = illegal.Replace(c.ToString(), ""); 
}

Ale muszę zgodzić się z komentarzami, prawdopodobnie spróbowałbym zająć się źródłem nielegalnych ścieżek, zamiast próbować przekształcić nielegalną ścieżkę w legalną, ale prawdopodobnie niezamierzoną.

Edycja: Lub potencjalnie „lepsze” rozwiązanie przy użyciu Regex.

string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
illegal = r.Replace(illegal, "");

Wciąż jednak pojawia się pytanie, dlaczego to robisz.

Matthew Scharley
źródło
40
Nie jest konieczne dołączanie dwóch list razem. Lista znaków niedozwolonej nazwy pliku zawiera listę znaków niedozwolonej ścieżki i zawiera kilka innych. Oto listy obu list obsadzone w int: 34, 60,62,124,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,58,42,63,92,47 34,60,62,124,0,1,2 , 3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27 , 28, 29, 30, 31
Sarel Botha,
9
@sjbotha może to być prawda w przypadku implementacji platformy .NET przez system Windows i Microsoft. Nie jestem skłonny przyjąć tego samego założenia dla powiedzmy mono z systemem Linux.
Matthew Scharley,
7
Odnośnie pierwszego rozwiązania. Czy StringBuilder nie powinien być bardziej wydajny niż przypisania ciągów?
epignosisx
6
Za to, co jest warte, @MatthewScharley, implementacja Mono GetInvalidPathChars () zwraca tylko 0x00, a GetInvalidFileNameChars () zwraca tylko 0x00 i '/', gdy działa na platformach innych niż Windows. W systemie Windows listy nieprawidłowych znaków są znacznie dłuższe, a GetInvalidPathChars () jest całkowicie zduplikowany w GetInvalidFileNameChars (). Nie zmieni się to w dającej się przewidzieć przyszłości, więc wszystko, co naprawdę robisz, to podwojenie czasu potrzebnego do uruchomienia tej funkcji, ponieważ obawiasz się, że definicja prawidłowej ścieżki wkrótce się zmieni. Który nie będzie.
Warren Rumak
13
@Charleh ta dyskusja jest tak niepotrzebna ... kod powinien być zawsze optymalizowany i nie ma ryzyka, że ​​będzie on niepoprawny. Nazwa pliku jest również częścią ścieżki. Jest więc po prostu nielogiczne, że GetInvalidPathChars()może zawierać postacie, które GetInvalidFileNameChars()tego nie zrobią. Nie przejmujesz się poprawnością w stosunku do „przedwczesnej” optymalizacji. Po prostu używasz złego kodu.
Stefan Fabian,
352

Pierwotne pytanie „usuwać nielegalne znaki”:

public string RemoveInvalidChars(string filename)
{
    return string.Concat(filename.Split(Path.GetInvalidFileNameChars()));
}

Zamiast tego możesz je zastąpić:

public string ReplaceInvalidChars(string filename)
{
    return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));    
}

Ta odpowiedź była w innym wątku autorstwa Ceres , bardzo podoba mi się, że jest schludna i prosta.

Shehab Fawzy
źródło
10
Aby precyzyjnie odpowiedzieć na pytanie PO, musisz użyć „” zamiast „_”, ale Twoja odpowiedź prawdopodobnie dotyczy więcej z nas w praktyce. Myślę, że zastępowanie nielegalnych postaci niektórymi legalnymi jest częstsze.
BH
35
Przetestowałem pięć metod z tego pytania (pętla czasowa 100 000) i ta metoda jest najszybsza. Wyrażenie regularne zajęło 2 miejsce i było o 25% wolniejsze niż ta metoda.
Brain2000
10
Aby odpowiedzieć na komentarz @BH, wystarczy po prostu użyć string.Concat (name.Split (Path.GetInvalidFileNameChars ()))
Michael Sutton
210

Używam Linq do czyszczenia nazw plików. Możesz łatwo to rozszerzyć, aby sprawdzić również prawidłowe ścieżki.

private static string CleanFileName(string fileName)
{
    return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));
}

Aktualizacja

Niektóre komentarze wskazują, że ta metoda nie działa dla nich, dlatego zamieściłem link do fragmentu DotNetFiddle, abyś mógł sprawdzić poprawność metody.

https://dotnetfiddle.net/nw1SWY

Michael Minton
źródło
4
To mi nie zadziałało. Metoda nie zwraca czystego ciągu. Zwraca przekazaną nazwę pliku taką, jaka jest.
Karan
To, co powiedział @Karan, nie działa, oryginalny ciąg znaków powraca.
Jon
Rzeczywiście można to zrobić z Linq jak ten jednak: var invalid = new HashSet<char>(Path.GetInvalidPathChars()); return new string(originalString.Where(s => !invalid.Contains(s)).ToArray()). Wydajność prawdopodobnie nie jest świetna, ale to chyba nie ma znaczenia.
Casey,
2
@Karan lub Jon Jakie dane wysyłasz tę funkcję? Zobacz moją edycję, aby sprawdzić tę metodę.
Michael Minton
3
To proste - chłopaki podawali ciągi znaków z ważnymi znakami. Wybrany za fajne rozwiązanie z agregatem.
Nickmaovich,
89

Możesz usunąć nielegalne znaki przy użyciu Linq w następujący sposób:

var invalidChars = Path.GetInvalidFileNameChars();

var invalidCharsRemoved = stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray();

EDYCJA
Tak to wygląda z wymaganą edycją wspomnianą w komentarzach:

var invalidChars = Path.GetInvalidFileNameChars();

string invalidCharsRemoved = new string(stringWithInvalidChars
  .Where(x => !invalidChars.Contains(x))
  .ToArray());
Gregor Slavec
źródło
1
Podoba mi się w ten sposób: przechowujesz tylko dozwolone znaki w ciągu (co jest niczym innym jak tablicą znaków).
Koleś Pascalou,
6
Wiem, że to stare pytanie, ale to niesamowita odpowiedź. Chciałem jednak dodać, że w c # nie można rzutować z char [] na string ani pośrednio, ani jawnie (szalone, wiem), więc musisz upuścić go do konstruktora łańcucha.
JNYRanger
1
Nie potwierdziłem tego, ale oczekuję, że Path.GetInvalidPathChars () będzie nadzbiorem GetInvalidFileNameChars () i obejmie zarówno nazwy plików, jak i ścieżki, więc prawdopodobnie użyłbym tego.
angularsen
3
@anjdreas faktycznie Path.GetInvalidPathChars () wydaje się być podzbiorem Path.GetInvalidFileNameChars (), a nie odwrotnie. Path.GetInvalidPathChars () nie zwróci na przykład „?”.
Rafael Costa
1
To dobra odpowiedź. Używam zarówno listy nazw plików, jak i listy ścieżek plików: ____________________________ ciąg cleanData = nowy ciąg (data.Where (x =>! Path.GetInvalidFileNameChars (). Zawiera (x) &&! Path.GetInvalidPathChars (). Zawiera (x)). ToArray ());
bramka
27

Są to świetne rozwiązania, ale wszystkie na nich polegają Path.GetInvalidFileNameChars, co może nie być tak niezawodne, jak mogłoby się wydawać. Zwróć uwagę na następujące uwagi w dokumentacji MSDN na Path.GetInvalidFileNameChars:

Nie ma gwarancji, że tablica zwrócona z tej metody będzie zawierać pełny zestaw znaków, które są niepoprawne w nazwach plików i katalogów. Pełny zestaw nieprawidłowych znaków może się różnić w zależności od systemu plików. Na przykład na platformach komputerowych z systemem Windows nieprawidłowe znaki ścieżki mogą zawierać znaki ASCII / Unicode od 1 do 31, a także cudzysłów ("), mniej niż (<), większy niż (>), potok (|), backspace ( \ b), null (\ 0) i tab (\ t).

Path.GetInvalidPathCharsMetoda nie jest lepsza . Zawiera dokładnie tę samą uwagę.

René
źródło
13
Jaki jest zatem sens Path.GetInvalidFileNameChars? Spodziewałbym się, że zwróci dokładnie niepoprawne znaki dla bieżącego systemu, polegając na .NET, aby dowiedzieć się, na którym systemie plików pracuję i prezentując mi pasujące niepoprawne znaki. Jeśli tak nie jest i zwraca po prostu znaki zakodowane na stałe, które w pierwszej kolejności nie są wiarygodne, metoda ta powinna zostać usunięta, ponieważ ma wartość zerową.
Jan
1
Wiem, że to stary komentarz, ale @Jan możesz chcieć pisać na innym systemie plików, może dlatego pojawia się ostrzeżenie.
fantastik78
3
@ fantastik78 dobra uwaga, ale w tym przypadku chciałbym mieć dodatkowy argument wyliczający, aby określić mój zdalny FS. Jeśli jest to zbyt duży wysiłek konserwacyjny (co jest najbardziej prawdopodobne), cała ta metoda jest nadal złym pomysłem, ponieważ daje błędne wrażenie bezpieczeństwa.
stycznia
1
@Jan Całkowicie się z tobą zgadzam, tylko kłóciłem się o ostrzeżenie.
fantastik78
Co ciekawe, jest to rodzaj „czarnej listy” nieprawidłowych znaków. Czy nie byłoby lepiej „dodać do białej listy” tylko znanych ważnych znaków ?! Przypomina mi głupi pomysł „virusscanner” zamiast białej listy dozwolonych aplikacji ....
Bernhard
26

W przypadku nazw plików:

var cleanFileName = string.Join("", fileName.Split(Path.GetInvalidFileNameChars()));

Aby uzyskać pełne ścieżki:

var cleanPath = string.Join("", path.Split(Path.GetInvalidPathChars()));

Zauważ, że jeśli zamierzasz użyć tego jako funkcji bezpieczeństwa, bardziej niezawodnym podejściem byłoby rozwinięcie wszystkich ścieżek, a następnie sprawdzenie, czy podana przez użytkownika ścieżka jest rzeczywiście dzieckiem katalogu, do którego użytkownik powinien mieć dostęp.

Lily Finley
źródło
18

Na początek Trim usuwa tylko znaki z początku lub końca łańcucha . Po drugie, powinieneś ocenić, czy naprawdę chcesz usunąć obraźliwe postacie, czy też szybko zawieść i powiadomić użytkownika, że ​​jego nazwa pliku jest nieprawidłowa. Mój wybór jest ten drugi, ale moja odpowiedź powinna przynajmniej pokazać, jak robić rzeczy we właściwy sposób I w niewłaściwy sposób:

Pytanie StackOverflow pokazujące, jak sprawdzić, czy dany ciąg jest prawidłową nazwą pliku . Uwaga: możesz użyć wyrażenia regularnego z tego pytania, aby usunąć znaki z zamianą wyrażeń regularnych (jeśli naprawdę musisz to zrobić).

użytkownik7116
źródło
W szczególności zgadzam się z drugą radą.
OregonGhost,
4
Normalnie zgodziłbym się z drugim, ale mam program, który generuje nazwę pliku i który może zawierać niedozwolone znaki w niektórych sytuacjach. Ponieważ mój program generuje nielegalne nazwy plików, uważam, że należy usunąć / zamienić te znaki. (Po prostu wskazując prawidłowy przypadek użycia)
JDB wciąż pamięta Monikę
16

Najlepszym sposobem na usunięcie niedozwolonego znaku z danych wejściowych użytkownika jest zastąpienie niedozwolonego znaku przy użyciu klasy Regex, utworzenie metody w kodzie z tyłu lub sprawdzenie poprawności po stronie klienta za pomocą kontrolki RegularExpression.

public string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_]+", "_", RegexOptions.Compiled);
}

LUB

<asp:RegularExpressionValidator ID="regxFolderName" 
                                runat="server" 
                                ErrorMessage="Enter folder name with  a-z A-Z0-9_" 
                                ControlToValidate="txtFolderName" 
                                Display="Dynamic" 
                                ValidationExpression="^[a-zA-Z0-9_]*$" 
                                ForeColor="Red">
anomepani
źródło
5
IMHO to rozwiązanie jest znacznie lepsze niż inne Zamiast szukać wszystkich niepoprawnych znaków, wystarczy zdefiniować, które są poprawne.
igorushi
15

Używam do tego wyrażeń regularnych. Po pierwsze, dynamicznie buduję wyrażenie regularne.

string regex = string.Format(
                   "[{0}]",
                   Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

Następnie po prostu wywołuję removeInvalidChars.Replace, aby znaleźć i zamienić. Można to oczywiście rozszerzyć również na znaki ścieżki.

Jeff Yates
źródło
Dziwne, działało dla mnie. Sprawdzę to dwukrotnie, kiedy będę miał szansę. Czy możesz być bardziej szczegółowy i wyjaśnić, co dokładnie nie działa dla Ciebie?
Jeff Yates
1
To nie zadziała (przynajmniej właściwie), ponieważ nie uciekasz poprawnie od znaków ścieżki, a niektóre z nich mają specjalne znaczenie. Zobacz, jak to zrobić.
Matthew Scharley,
@Jeff: Twoja wersja jest wciąż lepsza niż Matthew, jeśli ją nieco zmodyfikujesz. Zobacz moją odpowiedź, w jaki sposób.
stycznia
2
Dodałbym również inne niepoprawne wzorce nazw plików, które można znaleźć w witrynie MSDN, i rozszerzę twoje rozwiązanie do następującego wyrażenia regularnego:new Regex(String.Format("^(CON|PRN|AUX|NUL|CLOCK\$|COM[1-9]|LPT[1-9])(?=\..|$)|(^(\.+|\s+)$)|((\.+|\s+)$)|([{0}])", Regex.Escape(new String(Path.GetInvalidFileNameChars()))), RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant);
yar_shukan
13

Absolutnie wolę pomysł Jeffa Yatesa. Będzie działać idealnie, jeśli nieznacznie go zmodyfikujesz:

string regex = String.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

Ulepszenie polega na uniknięciu automatycznie generowanego wyrażenia regularnego.

Jan
źródło
11

Oto fragment kodu, który powinien pomóc w .NET 3 i nowszych wersjach.

using System.IO;
using System.Text.RegularExpressions;

public static class PathValidation
{
    private static string pathValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex pathValidator = new Regex(pathValidatorExpression, RegexOptions.Compiled);

    private static string fileNameValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex fileNameValidator = new Regex(fileNameValidatorExpression, RegexOptions.Compiled);

    private static string pathCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex pathCleaner = new Regex(pathCleanerExpression, RegexOptions.Compiled);

    private static string fileNameCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex fileNameCleaner = new Regex(fileNameCleanerExpression, RegexOptions.Compiled);

    public static bool ValidatePath(string path)
    {
        return pathValidator.IsMatch(path);
    }

    public static bool ValidateFileName(string fileName)
    {
        return fileNameValidator.IsMatch(fileName);
    }

    public static string CleanPath(string path)
    {
        return pathCleaner.Replace(path, "");
    }

    public static string CleanFileName(string fileName)
    {
        return fileNameCleaner.Replace(fileName, "");
    }
}
James
źródło
8

Większość powyższych rozwiązań łączy niedozwolone znaki zarówno dla ścieżki, jak i nazwy pliku, co jest niepoprawne (nawet gdy oba wywołania obecnie zwracają ten sam zestaw znaków). Najpierw podzieliłem ścieżkę + nazwę pliku na ścieżkę i nazwę pliku, a następnie zastosowałem odpowiedni zestaw do jednego z nich, a następnie połączyłem je ponownie.

wvd_vegt

wvd_vegt
źródło
+1: Bardzo prawda. Dzisiaj, pracując w .NET 4.0, rozwiązanie wyrażenia regularnego z pierwszej odpowiedzi nuknęło wszystkie ukośniki odwrotne na pełnej ścieżce. Dlatego stworzyłem regex dla ścieżki dir i regex dla samej nazwy pliku, oczyszczone osobno i zrekombinowane
dario_ramos
To może być prawda, ale to nie odpowiada na pytanie. Nie jestem pewien, czy niejasne „zrobiłbym to w ten sposób” jest strasznie pomocne w porównaniu z niektórymi kompletnymi rozwiązaniami już tutaj dostępnymi (patrz na przykład odpowiedź Lilly poniżej)
Ian Grainger
6

Jeśli usuniesz lub zastąpisz jednym znakiem nieprawidłowe znaki, możesz mieć kolizje:

<abc -> abc
>abc -> abc

Oto prosta metoda, aby tego uniknąć:

public static string ReplaceInvalidFileNameChars(string s)
{
    char[] invalidFileNameChars = System.IO.Path.GetInvalidFileNameChars();
    foreach (char c in invalidFileNameChars)
        s = s.Replace(c.ToString(), "[" + Array.IndexOf(invalidFileNameChars, c) + "]");
    return s;
}

Wynik:

 <abc -> [1]abc
 >abc -> [2]abc
Maxence
źródło
5

Rzuć wyjątek.

if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
            {
                throw new ArgumentException();
            }
Mirezus
źródło
4

Napisałem tego potwora dla zabawy, pozwala ci to w obie strony:

public static class FileUtility
{
    private const char PrefixChar = '%';
    private static readonly int MaxLength;
    private static readonly Dictionary<char,char[]> Illegals;
    static FileUtility()
    {
        List<char> illegal = new List<char> { PrefixChar };
        illegal.AddRange(Path.GetInvalidFileNameChars());
        MaxLength = illegal.Select(x => ((int)x).ToString().Length).Max();
        Illegals = illegal.ToDictionary(x => x, x => ((int)x).ToString("D" + MaxLength).ToCharArray());
    }

    public static string FilenameEncode(string s)
    {
        var builder = new StringBuilder();
        char[] replacement;
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if(Illegals.TryGetValue(c,out replacement))
                {
                    builder.Append(PrefixChar);
                    builder.Append(replacement);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static string FilenameDecode(string s)
    {
        var builder = new StringBuilder();
        char[] buffer = new char[MaxLength];
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if (c == PrefixChar)
                {
                    reader.Read(buffer, 0, MaxLength);
                    var encoded =(char) ParseCharArray(buffer);
                    builder.Append(encoded);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static int ParseCharArray(char[] buffer)
    {
        int result = 0;
        foreach (char t in buffer)
        {
            int digit = t - '0';
            if ((digit < 0) || (digit > 9))
            {
                throw new ArgumentException("Input string was not in the correct format");
            }
            result *= 10;
            result += digit;
        }
        return result;
    }
}
Johan Larsson
źródło
1
Podoba mi się to, ponieważ pozwala uniknąć dwóch różnych ciągów tworzących tę samą ścieżkę wynikową.
Kim
3

Myślę, że o wiele łatwiej jest sprawdzić poprawność za pomocą wyrażenia regularnego i określić, które znaki są dozwolone, zamiast próbować sprawdzić wszystkie złe znaki. Zobacz te linki: http://www.c-sharpcorner.com/UploadFile/prasad_1/RegExpPSD12062005021717AM/RegExpPSD.aspx http://www.windowsdevcenter.com/pub/a/oreilly/windows/news/csharp_0101.html

Poszukaj też „edytora wyrażeń regularnych”, bardzo pomagają. Istnieje kilka, wokół których nawet wypisuje kod w c # dla ciebie.

Sandor Davidhazi
źródło
Biorąc pod uwagę, że .net jest strukturą, która ma na celu umożliwienie uruchamiania programów na wielu platformach (np. Linux / Unix oraz Windows), uważam, że Path.GetInvalidFileNameChars () jest najlepszy, ponieważ będzie zawierał wiedzę o tym, co jest lub nie jest t obowiązuje w systemie plików, na którym uruchamiany jest program. Nawet jeśli twój program nigdy nie będzie działał w systemie Linux (być może jest on pełen kodu WPF), zawsze istnieje szansa, że ​​jakiś nowy system plików Windows pojawi się w przyszłości i będzie miał inne ważne / nieprawidłowe znaki. Realizacja własnego wyrażenia regularnego to nowe odkrycie koła i przeniesienie problemu z platformą do własnego kodu.
Daniel Scott
Zgadzam się jednak z twoją radą dotyczącą redaktorów / testerów regex online. Uważam je za nieocenione (ponieważ wyrażenia regularne są trudnymi rzeczami i pełne subtelności, które mogą z łatwością cię wyolbrzymić, dając wyrażenie regularne, które zachowuje się w bardzo nieoczekiwany sposób z przypadkami na krawędziach). Moim ulubionym jest regex101.com (podoba mi się, jak rozkłada regex i pokazuje jasno, co spodziewa się dopasować). Lubię też debuggex.com, ponieważ ma kompaktową graficzną reprezentację grup dopasowania i klas postaci i tak dalej.
Daniel Scott
3

Wydaje się, że jest to O (n) i nie wydaje zbyt dużo pamięci na ciągi znaków:

    private static readonly HashSet<char> invalidFileNameChars = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string RemoveInvalidFileNameChars(string name)
    {
        if (!name.Any(c => invalidFileNameChars.Contains(c))) {
            return name;
        }

        return new string(name.Where(c => !invalidFileNameChars.Contains(c)).ToArray());
    }
Alexey F.
źródło
1
Nie sądzę, że to O (n), kiedy używasz funkcji „Dowolna”.
II STRZAŁKI
@IIARROWS i co według Ciebie jest?
Alexey F
Nie wiem, po prostu nie czułem się tak, kiedy pisałem swój komentarz ... teraz, gdy próbowałem go obliczyć, wygląda na to, że masz rację.
II STRZAŁKI
Wybrałem ten ze względu na twoje rozważania dotyczące wydajności. Dzięki.
Berend Engelbrecht
3

Przeglądając tutaj odpowiedzi, wszystkie ** wydają się wymagać użycia tablicy znaków niepoprawnych nazw plików.

To prawda, że ​​może to być mikrooptymalizacja - ale z korzyścią dla każdego, kto może chcieć sprawdzić dużą liczbę wartości pod kątem poprawności nazw plików, warto zauważyć, że zbudowanie zestawu nieważnych znaków przyniesie znacznie lepszą wydajność.

Byłem bardzo zaskoczony (zszokowany) w przeszłości, jak szybko hashset (lub słownik) osiąga lepsze wyniki niż iteracja po liście. W przypadku łańcuchów jest to absurdalnie niska liczba (około 5-7 pozycji z pamięci). W przypadku większości innych prostych danych (odniesienia do obiektów, liczby itp.) Magiczna krzyżówka wydaje się zawierać około 20 elementów.

Na „liście” Path.InvalidFileNameChars znajduje się 40 nieprawidłowych znaków. Przeprowadziłem dzisiaj wyszukiwanie, a na StackOverflow znajduje się całkiem niezły test porównawczy, który pokazuje, że zestaw skrótów zajmie nieco ponad połowę czasu tablicy / listy dla 40 elementów: https://stackoverflow.com/a/10762995/949129

Oto klasa pomocnicza, której używam do dezynfekcji ścieżek. Zapominam teraz, dlaczego miałem w sobie fantazyjną opcję wymiany, ale jest to urocza premia.

Dodatkowa metoda premiowa „IsValidLocalPath” też :)

(** te, które nie używają wyrażeń regularnych)

public static class PathExtensions
{
    private static HashSet<char> _invalidFilenameChars;
    private static HashSet<char> InvalidFilenameChars
    {
        get { return _invalidFilenameChars ?? (_invalidFilenameChars = new HashSet<char>(Path.GetInvalidFileNameChars())); }
    }


    /// <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 remove bad characters.</param>
    /// <param name="fancyReplacements">TRUE 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, "_" is returned.</returns>
    public static string ToValidFilename(this string text, char? replacement = '_', bool fancyReplacements = false)
    {
        StringBuilder sb = new StringBuilder(text.Length);
        HashSet<char> invalids = InvalidFilenameChars;
        bool changed = false;

        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            if (invalids.Contains(c))
            {
                changed = true;
                char repl = replacement ?? '\0';
                if (fancyReplacements)
                {
                    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;
    }


    /// <summary>
    /// Returns TRUE if the specified path is a valid, local filesystem path.
    /// </summary>
    /// <param name="pathString"></param>
    /// <returns></returns>
    public static bool IsValidLocalPath(this string pathString)
    {
        // From solution at https://stackoverflow.com/a/11636052/949129
        Uri pathUri;
        Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri);
        return isValidUri && pathUri != null && pathUri.IsLoopback;
    }
}
Daniel Scott
źródło
2
public static class StringExtensions
      {
        public static string RemoveUnnecessary(this string source)
        {
            string result = string.Empty;
            string regex = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
            Regex reg = new Regex(string.Format("[{0}]", Regex.Escape(regex)));
            result = reg.Replace(source, "");
            return result;
        }
    }

Możesz wyraźnie użyć metody.

aemre
źródło
2

Nazwa pliku nie może zawierać znaków z Path.GetInvalidPathChars(), +oraz #symbole, nazwy i inne specyficzne. Wszystkie czeki połączyliśmy w jedną klasę:

public static class FileNameExtensions
{
    private static readonly Lazy<string[]> InvalidFileNameChars =
        new Lazy<string[]>(() => Path.GetInvalidPathChars()
            .Union(Path.GetInvalidFileNameChars()
            .Union(new[] { '+', '#' })).Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray());


    private static readonly HashSet<string> ProhibitedNames = new HashSet<string>
    {
        @"aux",
        @"con",
        @"clock$",
        @"nul",
        @"prn",

        @"com1",
        @"com2",
        @"com3",
        @"com4",
        @"com5",
        @"com6",
        @"com7",
        @"com8",
        @"com9",

        @"lpt1",
        @"lpt2",
        @"lpt3",
        @"lpt4",
        @"lpt5",
        @"lpt6",
        @"lpt7",
        @"lpt8",
        @"lpt9"
    };

    public static bool IsValidFileName(string fileName)
    {
        return !string.IsNullOrWhiteSpace(fileName)
            && fileName.All(o => !IsInvalidFileNameChar(o))
            && !IsProhibitedName(fileName);
    }

    public static bool IsProhibitedName(string fileName)
    {
        return ProhibitedNames.Contains(fileName.ToLower(CultureInfo.InvariantCulture));
    }

    private static string ReplaceInvalidFileNameSymbols([CanBeNull] this string value, string replacementValue)
    {
        if (value == null)
        {
            return null;
        }

        return InvalidFileNameChars.Value.Aggregate(new StringBuilder(value),
            (sb, currentChar) => sb.Replace(currentChar, replacementValue)).ToString();
    }

    public static bool IsInvalidFileNameChar(char value)
    {
        return InvalidFileNameChars.Value.Contains(value.ToString(CultureInfo.InvariantCulture));
    }

    public static string GetValidFileName([NotNull] this string value)
    {
        return GetValidFileName(value, @"_");
    }

    public static string GetValidFileName([NotNull] this string value, string replacementValue)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentException(@"value should be non empty", nameof(value));
        }

        if (IsProhibitedName(value))
        {
            return (string.IsNullOrWhiteSpace(replacementValue) ? @"_" : replacementValue) + value; 
        }

        return ReplaceInvalidFileNameSymbols(value, replacementValue);
    }

    public static string GetFileNameError(string fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            return CommonResources.SelectReportNameError;
        }

        if (IsProhibitedName(fileName))
        {
            return CommonResources.FileNameIsProhibited;
        }

        var invalidChars = fileName.Where(IsInvalidFileNameChar).Distinct().ToArray();

        if(invalidChars.Length > 0)
        {
            return string.Format(CultureInfo.CurrentCulture,
                invalidChars.Length == 1 ? CommonResources.InvalidCharacter : CommonResources.InvalidCharacters,
                StringExtensions.JoinQuoted(@",", @"'", invalidChars.Select(c => c.ToString(CultureInfo.CurrentCulture))));
        }

        return string.Empty;
    }
}

Metoda GetValidFileNamezastępuje wszystkie niepoprawne dane _.

Obrona
źródło
2

Jedna linijka do czyszczenia ciągu z jakichkolwiek niedozwolonych znaków dla nazewnictwa plików Windows:

public static string CleanIllegalName(string p_testName) => new Regex(string.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars())))).Replace(p_testName, "");
Zananok
źródło
1
public static bool IsValidFilename(string testName)
{
    return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);
}
Mbdavis
źródło
0

Spowoduje to, że chcesz i unikniesz kolizji

 static string SanitiseFilename(string key)
    {
        var invalidChars = Path.GetInvalidFileNameChars();
        var sb = new StringBuilder();
        foreach (var c in key)
        {
            var invalidCharIndex = -1;
            for (var i = 0; i < invalidChars.Length; i++)
            {
                if (c == invalidChars[i])
                {
                    invalidCharIndex = i;
                }
            }
            if (invalidCharIndex > -1)
            {
                sb.Append("_").Append(invalidCharIndex);
                continue;
            }

            if (c == '_')
            {
                sb.Append("__");
                continue;
            }

            sb.Append(c);
        }
        return sb.ToString();

    }
mcintyre321
źródło
0

Wydaje mi się, że na pytanie, na które nie ma jeszcze pełnej odpowiedzi ... Odpowiedzi opisują tylko czystą nazwę pliku LUB ścieżkę ... Oto moje rozwiązanie:

private static string CleanPath(string path)
{
    string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
    Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
    List<string> split = path.Split('\\').ToList();
    string returnValue = split.Aggregate(string.Empty, (current, s) => current + (r.Replace(s, "") + @"\"));
    returnValue = returnValue.TrimEnd('\\');
    return returnValue;
}
Suplanus
źródło
0

Stworzyłem metodę rozszerzenia, która łączy kilka sugestii:

  1. Trzymanie nielegalnych znaków w zestawie skrótów
  2. Filtrowanie znaków poniżej ascii 127. Ponieważ Path.GetInvalidFileNameChars nie zawiera wszystkich możliwych nieprawidłowych znaków dla kodów ascii od 0 do 255. Zobacz tutaj i MSDN
  3. Możliwość zdefiniowania znaku zastępującego

Źródło:

public static class FileNameCorrector
{
    private static HashSet<char> invalid = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string ToValidFileName(this string name, char replacement = '\0')
    {
        var builder = new StringBuilder();
        foreach (var cur in name)
        {
            if (cur > 31 && cur < 128 && !invalid.Contains(cur))
            {
                builder.Append(cur);
            }
            else if (replacement != '\0')
            {
                builder.Append(replacement);
            }
        }

        return builder.ToString();
    }
}
schoetbi
źródło
0

Oto funkcja, która zastępuje wszystkie niedozwolone znaki w nazwie pliku znakiem zastępującym:

public static string ReplaceIllegalFileChars(string FileNameWithoutPath, char ReplacementChar)
{
  const string IllegalFileChars = "*?/\\:<>|\"";
  StringBuilder sb = new StringBuilder(FileNameWithoutPath.Length);
  char c;

  for (int i = 0; i < FileNameWithoutPath.Length; i++)
  {
    c = FileNameWithoutPath[i];
    if (IllegalFileChars.IndexOf(c) >= 0)
    {
      c = ReplacementChar;
    }
    sb.Append(c);
  }
  return (sb.ToString());
}

Na przykład znak podkreślenia może być użyty jako znak zastępczy:

NewFileName = ReplaceIllegalFileChars(FileName, '_');
Hans-Peter Kalb
źródło
Oprócz udzielonej odpowiedzi rozważ krótkie wyjaśnienie, dlaczego i jak to rozwiązuje problem.
jtate
-7

Lub możesz po prostu zrobić

[YOUR STRING].Replace('\\', ' ').Replace('/', ' ').Replace('"', ' ').Replace('*', ' ').Replace(':', ' ').Replace('?', ' ').Replace('<', ' ').Replace('>', ' ').Replace('|', ' ').Trim();
Danny Fallas
źródło