web-dev-qa-db-ja.com

パスとファイル名から不正な文字を削除するにはどうすればいいですか?

単純な文字列から不正なパスやファイルの文字を削除するための堅牢で簡単な方法が必要です。私は以下のコードを使用しましたが、それは何もしないようです、何が足りないのですか?

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

代わりにこのようなものを試してください。

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

しかし、私はそのコメントに同意しなければなりません。違法なパスを合法的だがおそらく意図しないパスに変換するのではなく、違法なパスの原因に対処しようとします。

編集:または正規表現を使用して、潜在的に「より良い」解決策。

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, "");

それでも、最初にそもそもなぜこれをしているのか、という疑問が投げかけられます。

458
public string GetSafeFilename(string filename)
{

    return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));

}

この答えはCeresによる別のスレッドでのものでした 、私は本当にきちんとしたシンプルなものが好きです。

257
Shehab Fawzy

Linqを使ってファイル名を整理します。有効なパスをチェックするためにこれを簡単に拡張することができます。

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

更新

このメソッドが機能していないことを示すコメントがいくつかあります。そのため、DotNetFiddleスニペットへのリンクを含めて、このメソッドを検証できるようにします。

https://dotnetfiddle.net/nw1SWY

204
Michael Minton

あなたはこのようにLinqを使って違法な文字を削除することができます:

var invalidChars = Path.GetInvalidFileNameChars();

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

EDIT
これは、コメントに記載されている必須の編集内容の外観です。

var invalidChars = Path.GetInvalidFileNameChars();

string invalidCharsRemoved = new string(stringWithInvalidChars
  .Where(x => !invalidChars.Contains(x))
  .ToArray());
87
Gregor Slavec

これらはすべて優れた解決策ですが、Path.GetInvalidFileNameCharsに頼っているため、思ったほど信頼性が高くない可能性があります。 Path.GetInvalidFileNameChars に関するMSDNドキュメントの次の注意に注意してください。

このメソッドから返される配列はファイル名やディレクトリ名に無効な文字の完全なセットを含むことは保証されていません。無効な文字のフルセットはファイルシステムによって異なります。たとえば、Windowsベースのデスクトッププラットフォームでは、無効なパス文字には、1から31までのASCII/Unicode文字、引用符( ")、小なり(<)、大なり(>)、パイプ(|)、バックスペース()\b)、null(\ 0)、およびタブ(\ t)。

Path.GetInvalidPathChars メソッドではそれほど良くありません。それは全く同じ発言を含みます。

26
René

ファイル名の場合:

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

フルパスの場合:

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

これをセキュリティ機能として使用する場合は、すべてのパスを拡張し、ユーザーが指定したパスが実際にユーザーがアクセスできるディレクトリの子であることを確認することをお勧めします。

19
Lily Finley

手始めに、 Trimは文字列の先頭または末尾から文字を削除するだけです 。次に、攻撃的な文字を本当に削除するのか、それともすぐに失敗してユーザーにそのファイル名が無効であるのかを知らせる必要があるかどうかを評価する必要があります。私の選択は後者ですが、私の答えは少なくとも物事を正しい方法と間違った方法で行う方法をあなたに示すべきです:

与えられた文字列が有効なファイル名であるかどうかをチェックする方法を示すStackOverflowの質問 。この質問からの正規表現を使用して、正規表現を置き換えて文字を削除することができます(本当にこれを行う必要がある場合)。

18
user7116

これを実現するために正規表現を使います。まず、正規表現を動的に構築します。

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

それから検索と置換を行うためにremoveInvalidChars.Replaceを呼び出します。これは明らかにパス文字をカバーするために拡張することができます。

16
Jeff Yates

ユーザー入力から不正な文字を削除する最善の方法は、Regexクラスを使用して不正な文字を置き換える、コードビハインドでメソッドを作成する、またはRegularExpressionコントロールを使用してクライアント側で検証することです。

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

または

<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">
15
anomepani

私は絶対にJeff Yatesの考えを好みます。あなたが少しそれを修正すれば、それは完璧に動作します:

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

改善点は、自動的に生成された正規表現を回避することだけです。

14
Jan

これは、.NET 3以降で役立つはずのコードスニペットです。

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, "");
    }
}
11
James

上記のほとんどの解決策は(両方の呼び出しが現在同じ文字セットを返す場合であっても)パスとファイル名の両方に対して不正な文字を組み合わせています。最初にパスとファイル名をパスとファイル名に分割し、次に適切なセットをどちらか一方に適用してから2つを再び結合します。

wvd_vegt

8
wvd_vegt

無効な文字を1文字で削除または置き換えると、衝突が発生する可能性があります。

<abc -> abc
>abc -> abc

これを回避する簡単な方法は次のとおりです。

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

結果:

 <abc -> [1]abc
 >abc -> [2]abc
6
Maxence

例外を投げます。

if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
            {
                throw new ArgumentException();
            }
5
mirezus

私は楽しみのためにこのモンスターを書いた、それはあなたが往復することを可能にする:

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;
    }
}
4
Johan Larsson

すべての悪い文字を調べようとするよりも、正規表現を使用してどの文字が許可されるかを指定することを検証するほうがはるかに簡単だと思います。これらのリンクを参照してください。 http://www.c-sharpcorner.com/UploadFile/prasad_1/RegExpPSD12062005021717AM/RegExpPSD.aspxhttp://www.windowsdevcenter.com/pub/a/oreilly/windows/news/csharp_0101.html

また、 "正規表現エディタ"の検索を行う、彼らは非常に役立ちます。あなたのためにc#でコードを出力するものさえあるいくつかがあります。

3

これはO(n)のようで、文字列にあまりメモリを消費しません。

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

方法をわかりやすく使用できます。

2
aemre

ファイル名には、Path.GetInvalidPathChars()+、および#シンボルからの文字、およびその他の特定の名前を含めることはできません。すべてのチェックを1つのクラスにまとめました。

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

メソッドGetValidFileNameは、すべての不正なデータを_に置き換えます。

2
Backs

Windowsファイルの命名のための不正な文字から文字列をクリーンアップするための1つのライナー:

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, "");
2
Zananok

ここで答えを調べてみると、それらはすべて無効なファイル名文字のchar配列を使用しているようです。

確かに、これはマイクロ最適化になるかもしれません - しかし、有効なファイル名であることについて多数の値をチェックしようとしている誰かの利益のために、無効な文字のハッシュセットを作ることは著しく良いパフォーマンスをもたらすことに注目する価値があります。

ハッシュセット(または辞書)がリストを反復処理するよりもどれほど早くパフォーマンスが優れているか、私は過去に非常に驚いた(衝撃を受けた)。文字列では、それはばかげて低い数です(メモリから約5〜7項目)。他のほとんどの単純なデータ(オブジェクト参照、数値など)では、魔法のクロスオーバーはおよそ20項目になるようです。

Path.InvalidFileNameCharsの "リスト"に40個の無効な文字があります。今日検索しましたが、StackOverflowには40項目の配列/リストの半分以上の時間がかかることを示す非常に良いベンチマークがあります。 https://stackoverflow.com/a/10762995/949129

これがパスのサニタイズに使用するヘルパークラスです。私はなぜ私がそれに凝った交換オプションを持っていたかを今忘れます、しかしそれはかわいいボーナスとしてそこにあります。

追加のボーナスメソッド "IsValidLocalPath"も:)

(**正規表現を使わないもの)

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;
    }
}
2
Daniel Scott
public static bool IsValidFilename(string testName)
{
    return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);
}
1
mbdavis

いくつかの提案を組み合わせた拡張メソッドを作成しました。

  1. ハッシュセットに不正な文字を入れる
  2. Path.GetInvalidFileNameCharsには、0〜255のASCIIコードで使用可能なすべての無効な文字が含まれているわけではありません。 ここで および MSDN
  3. 置換文字を定義する可能性

ソース:

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

これはあなたが望むことを望み、衝突を避けます

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

    }
0
mcintyre321

私は質問がすでに完全に答えられていないと思います...答えはきれいなファイル名OR pathのみを記述します...両方ではありません。これが私の解決策です:

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