文字列をC#でファイルパスセーフにする方法はありますか?


92

私のプログラムはインターネットから任意の文字列を取得し、それらをファイル名に使用します。これらの文字列から不良文字を削除する簡単な方法はありますか、またはこのためのカスタム関数を作成する必要がありますか?


回答:


171

ああ、私は人々がどの文字が有効であるかを推測しようとするときそれを嫌います。完全に移植性がない(常にMonoを考える)ことに加えて、以前のコメントはどちらも25文字以上の無効な文字を逃しました。

'Clean just a filename
Dim filename As String = "salmnas dlajhdla kjha;dmas'lkasn"
For Each c In IO.Path.GetInvalidFileNameChars
    filename = filename.Replace(c, "")
Next

'See also IO.Path.GetInvalidPathChars

83
C#バージョン:foreach(Path.GetInvalidFileNameChars()のvar c){fileName = fileName.Replace(c、 '-'); }
jcollum 2010

8
このソリューションは名前の競合をどのように処理しますか?1つのファイル名に複数の文字列が一致する可能性があるようです(たとえば、「He​​ll?」と「Hell *」)。問題のある文字のみを削除してもよい場合は、問題ありません。それ以外の場合は、名前の競合を処理するように注意する必要があります。
Stefano Ricciardi

2
ファイルシステムの名前(およびパス)の長さの制限はどうですか?予約ファイル名(PRN CON)はどうですか?データと元の名前を保存する必要がある場合は、GUID名を持つ2つのファイルを使用できます。guid.txtとguid.dat
Jack

6
1つのライナー、楽しい結果= Path.GetInvalidFileNameChars()。Aggregate(result、(current、c)=> current.Replace(c、 '-'));
ポールノップ

1
@ PaulKnopf、JetBrainがそのコードの著作権を所有していないのは確かですか;)
Marcus

36

無効な文字を取り除くには:

static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars
var validFilename = new string(filename.Where(ch => !invalidFileNameChars.Contains(ch)).ToArray());

無効な文字を置き換えるには:

static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars and an _ for invalid ones
var validFilename = new string(filename.Select(ch => invalidFileNameChars.Contains(ch) ? '_' : ch).ToArray());

無効な文字を置き換えるには(そして、Hell *とHell $のような名前の競合を避けるため):

static readonly IList<char> invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars and replaces invalid chars with a unique letter (Moves the Char into the letter range of unicode, starting at "A")
var validFilename = new string(filename.Select(ch => invalidFileNameChars.Contains(ch) ? Convert.ToChar(invalidFileNameChars.IndexOf(ch) + 65) : ch).ToArray());

33

この質問は以前に も尋ねられており、以前に何度も指摘されているように、適切ではありません。IO.Path.GetInvalidFileNameChars

1つ目は、PRNやCONなど、予約済みでファイル名に使用できない名前が多数あることです。ルートフォルダでのみ許可されていない他の名前があります。ピリオドで終わる名前も使用できません。

次に、さまざまな長さの制限があります。ここでNTFSの完全なリストを読んでください

3番目に、他の制限があるファイルシステムにアタッチできます。たとえば、ISO 9660ファイル名は「-」で始めることはできませんが、含めることはできます。

4番目に、2つのプロセスが「任意に」同じ名前を選択した場合はどうしますか?

一般に、ファイル名に外部で生成された名前を使用することは悪い考えです。独自のプライベートファイル名を生成し、人間が読める名前を内部に保存することをお勧めします。


13
技術的には正確ですが、GetInvalidFileNameCharsは、使用する状況の80%以上に適しているため、適切な回答です。あなたの答えは、受け入れられた答えへのコメントとしてより適切だったと思います。
CubanX 2011年

4
DourHighArchに同意します。ファイルを内部的にGUIDとして保存し、データベースに格納されている「フレンドリ名」に対して参照します。ユーザーがWebサイト上のパスを制御できないようにしてください。そうしないと、ユーザーがweb.configを盗もうとします。あなたがそれをきれいにするためにURL書き換えを組み込んだ場合、それはデータベース内の一致したフレンドリーなURLに対してのみ機能します。
rtpHarry 2012年

22

私はグラウエンウルフに同意し、強くお勧めします Path.GetInvalidFileNameChars()

ここに私のC#の貢献があります:

string file = @"38?/.\}[+=n a882 a.a*/|n^%$ ad#(-))";
Array.ForEach(Path.GetInvalidFileNameChars(), 
      c => file = file.Replace(c.ToString(), String.Empty));

ps-これは本来あるべきものよりも不可解です-私は簡潔にしようとしました。


3
ここArray.ForEachだけforeachではなく、なぜ世界で使用するのですか
BlueRaja-Danny Pflughoeft 2012

9
さらに簡潔/不可解になりたい場合:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito 2012年

@ BlueRaja-DannyPflughoeftあなたはそれを遅くしたいので?
ジョナサンアレン

@Johnathan Allen、foreachがArray.ForEachよりも高速だと思う理由は何ですか?
Ryan Buddicom 2014年

5
@rbuddicom Array.ForEachはデリゲートを受け取ります。つまり、インライン化できない関数を呼び出す必要があります。短い文字列の場合、実際のロジックよりも関数呼び出しのオーバーヘッドにより多くの時間を費やすことになります。.NET Coreは、呼び出しを「仮想化解除」してオーバーヘッドを削減する方法を検討しています。
ジョナサンアレン

13

これが私のバージョンです:

static string GetSafeFileName(string name, char replace = '_') {
  char[] invalids = Path.GetInvalidFileNameChars();
  return new string(name.Select(c => invalids.Contains(c) ? replace : c).ToArray());
}

GetInvalidFileNameCharsの結果がどのように計算されるのかはわかりませんが、 "Get"はそれが重要であることを示唆しているため、結果をキャッシュします。さらに、これは無効な文字のセットを反復してソース文字列の文字を1つずつ置き換える上記のソリューションのように、複数回ではなく1回だけ入力文字列をトラバースします。また、私はWhereベースのソリューションが好きですが、無効な文字を削除するのではなく置き換えることを好みます。最後に、文字列を繰り返し処理するときに文字を文字列に変換することを避けるために、私の置換は正確に1文字です。

私はプロファイリングを行わないことをすべて言います-これは私にただ「感じた」だけです。:)


1
new HashSet<char>(Path.GetInvalidFileNameChars())O(n)列挙を回避するために行うことができます-マイクロ最適化。
TrueWill、2015年

12

これが私が現在使用している関数です(C#の例でjcollumに感謝します)。

public static string MakeSafeFilename(string filename, char replaceChar)
{
    foreach (char c in System.IO.Path.GetInvalidFileNameChars())
    {
        filename = filename.Replace(c, replaceChar);
    }
    return filename;
}

便宜上、これを「ヘルパー」クラスに配置しました。


7

すべての特殊文字をすばやく削除したい場合は、ファイル名でユーザーが読みやすい場合がありますが、これはうまく機能します。

string myCrazyName = "q`w^e!r@t#y$u%i^o&p*a(s)d_f-g+h=j{k}l|z:x\"c<v>b?n[m]q\\w;e'r,t.y/u";
string safeName = Regex.Replace(
    myCrazyName,
    "\W",  /*Matches any nonword character. Equivalent to '[^A-Za-z0-9_]'*/
    "",
    RegexOptions.IgnoreCase);
// safeName == "qwertyuiopasd_fghjklzxcvbnmqwertyu"

1
実際には、\W英数字以外([^A-Za-z0-9_])よりも多く一致します。すべてのUnicode「単語」文字(русский中文...など)も置き換えられません。しかし、これは良いことです。
イシュマエル

唯一の欠点は、これによっても削除される.ため、最初に拡張機能を抽出し、後で再度追加する必要があることです。
2015

5
static class Utils
{
    public static string MakeFileSystemSafe(this string s)
    {
        return new string(s.Where(IsFileSystemSafe).ToArray());
    }

    public static bool IsFileSystemSafe(char c)
    {
        return !Path.GetInvalidFileNameChars().Contains(c);
    }
}

5

文字列を次のように同等のBase64に変換してみませんか。

string UnsafeFileName = "salmnas dlajhdla kjha;dmas'lkasn";
string SafeFileName = Convert.ToBase64String(Encoding.UTF8.GetBytes(UnsafeFileName));

あなたがそれを読むことができるようにそれを元に戻したい場合:

UnsafeFileName = Encoding.UTF8.GetString(Convert.FromBase64String(SafeFileName));

これを使用して、ランダムな説明から一意の名前でPNGファイルを保存しました。


5

上記のDour High Archが投稿した関連するStackoverflow質問へのリンクから収集した情報に基づいて、ClipFlair (http://github.com/Zoomicon/ClipFlair)のStringExtensions静的クラス(Utils.Silverlightプロジェクト)に追加した内容を次に示します。

public static string ReplaceInvalidFileNameChars(this string s, string replacement = "")
{
  return Regex.Replace(s,
    "[" + Regex.Escape(new String(System.IO.Path.GetInvalidPathChars())) + "]",
    replacement, //can even use a replacement string of any length
    RegexOptions.IgnoreCase);
    //not using System.IO.Path.InvalidPathChars (deprecated insecure API)
}

2
private void textBoxFileName_KeyPress(object sender, KeyPressEventArgs e)
{
   e.Handled = CheckFileNameSafeCharacters(e);
}

/// <summary>
/// This is a good function for making sure that a user who is naming a file uses proper characters
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
internal static bool CheckFileNameSafeCharacters(System.Windows.Forms.KeyPressEventArgs e)
{
    if (e.KeyChar.Equals(24) || 
        e.KeyChar.Equals(3) || 
        e.KeyChar.Equals(22) || 
        e.KeyChar.Equals(26) || 
        e.KeyChar.Equals(25))//Control-X, C, V, Z and Y
            return false;
    if (e.KeyChar.Equals('\b'))//backspace
        return false;

    char[] charArray = Path.GetInvalidFileNameChars();
    if (charArray.Contains(e.KeyChar))
       return true;//Stop the character from being entered into the control since it is non-numerical
    else
        return false;            
}

1

これを使用すると、すばやく簡単に理解できます。

<Extension()>
Public Function MakeSafeFileName(FileName As String) As String
    Return FileName.Where(Function(x) Not IO.Path.GetInvalidFileNameChars.Contains(x)).ToArray
End Function

これstringIEnumerable、aがchar配列でありstringchar配列を取るコンストラクター文字列があるため機能します。


1

私の古いプロジェクトから、私はこのソリューションを見つけました。このソリューションは2年以上完全に機能しています。不正な文字を「!」に置き換えてから、二重の!!をチェックし、独自の文字を使用します。

    public string GetSafeFilename(string filename)
    {
        string res = string.Join("!", filename.Split(Path.GetInvalidFileNameChars()));

        while (res.IndexOf("!!") >= 0)
            res = res.Replace("!!", "!");

        return res;
    }

0

多くのanwerはPath.GetInvalidFileNameChars()私に悪い解決策のように思われる使用を提案しています。ハッカーは常に最終的にそれをバイパスする方法を見つけるため、ブラックリストではなくホワイトリストを使用することをお勧めします。

使用できるコードの例を次に示します。

    string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.