任意の文字列から有効なWindowsファイル名を作成する方法は?


97

「Foo:Bar」のような文字列をファイル名として使用したいのですが、Windowsでは「:」文字をファイル名に使用できません。

「Foo:Bar」を「Foo- Bar」のようなものに変える方法はありますか?


1
今日も同じことをした。なんらかの理由でSOをチェックしませんでしたが、とにかく答えを見つけました。
アーロンスミス

回答:


153

このようなものを試してください:

string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
   fileName = fileName.Replace(c, '_');
}

編集:

GetInvalidFileNameChars()は10または15文字を返すためStringBuilder、単純な文字列の代わりにを使用することをお勧めします。元のバージョンは時間がかかり、より多くのメモリを消費します。


1
必要に応じてStringBuilderを使用することもできますが、名前が短く、価値がないと思います。独自のメソッドを作成してchar []を作成し、1回の反復ですべての間違った文字を置き換えることもできます。機能しない場合を除き、常にシンプルに保つことをお勧めします。ボトルネックが悪化する可能性があります
Diego Jancic 2009年

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
文字列に2つ以上の無効な文字が含まれる確率は非常に小さいため、string.Replace()のパフォーマンスを気にすることは無意味です。
Serge Wautier、2011年

1
素晴らしい解決策はさておき、リシャーパーはこのLinqバージョンを提案しました。そこにパフォーマンスの改善の可能性はあるのでしょうか。パフォーマンスは私の最大の関心事ではないので、読みやすさのためにオリジナルを保持しました。しかし、誰かが興味を持っている場合は、ベンチマークの価値があるかもしれません
chrispepper1989

1
@AndyMする必要はありません。file.name.txt.pdf有効なPDFです。Windows .は、拡張機能の最後の部分のみを読み取ります。
Diego Jancic 2016年

33
fileName = fileName.Replace(":", "-") 

ただし、「:」だけがWindowsの不正な文字ではありません。また、以下を処理する必要があります。

/, \, :, *, ?, ", <, > and |

これらはSystem.IO.Path.GetInvalidFileNameChars();に含まれています。

また(Windowsの場合)、「。」ファイル名の唯一の文字にすることはできません(「。」、「..」、「...」などは両方とも無効です)。たとえば、「。」を使用してファイルに名前を付ける場合は注意してください。

echo "test" > .test.

「.test」という名前のファイルを生成します

最後に、本当に正しく操作したい場合は、注意が必要な特別なファイル名がいくつかありますWindowsでは、次の名前のファイルを作成できません。

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.

3
予約された名前を知りませんでした。理にかなっている
グレッグディーン

4
また、価値があるため、これらの予約済みの名前のいずれかで始まり、その後に小数が続くファイル名を作成することはできません。つまり、con.air.avi
John Conrad

「.foo」は有効なファイル名です。「CON」ファイル名を知りませんでした-それは何のためですか?
コンフィギュレー

スクラッチ。CONはコンソール用です。
コンフィギュレー

設定者に感謝します。答えを更新しました。正しい「.foo」は有効です。ただし、「。foo」。可能性のある望ましくない結果につながります。更新しました。
Phil Price

13

これは効率的ではありませんが、もっと楽しいです:)

var fileName = "foo:bar";
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
var cleanFileName = new string(fileName.Where(m => !invalidChars.Contains(m)).ToArray<char>());

12

に基づいて最適化されたバージョンが必要な場合は、StringBuilderこれを使用してください。オプションとして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;
}

素敵で読みやすいコードの+1。非常に読みやすく、バグに気づくことができます:P ..この関数は、変更がtrueになることはないため、常に元の文字列を返す必要があります。
Erti-Chris Eelmaa 2014

ありがとう、私はそれが今より良いと思います。あなたは彼らがオープンソースについて言うことを知っています、「多くの目はすべてのバグを浅くするので、ユニットテストを書く必要はありません」...
Qwertie

8

Linqこれが使用する受け入れられた回答のバージョンですEnumerable.Aggregate

string fileName = "something";

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

7

ディエゴには正しい解決策がありますが、そこには非常に小さな間違いが1つあります。使用されているstring.Replaceのバージョンはstring.Replace(char、char)である必要があります。string.Replace(char、string)はありません。

回答を編集できないか、マイナーな変更を加えただけです。

だからそれはする必要があります:

string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
   fileName = fileName.Replace(c, '_');
}

7

ディエゴの答えを少しひねります。

Unicodeを恐れていない場合は、無効な文字をそれらに類似した有効なUnicode記号に置き換えることで、忠実度を維持できます。これが、材木のカットリストを含む最近のプロジェクトで使用したコードです。

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

これは1⁄2” spruce.txt代わりにのようなファイル名を生成します1_2_ spruce.txt

はい、それは本当にうまくいきます:

エクスプローラーのサンプル

買い手責任負担

このトリックがNTFSで機能することは知っていましたが、FATおよびFAT32パーティションでも機能することを知って驚きました。これは、長いファイル名がWindows 95 / NT までさかのぼっUnicode保存されているためです。Win7、XP、さらにはLinuxベースのルーターでテストしたところ、問題はありませんでした。DOSBoxの中では同じことは言えません。

とはいえ、これに夢中になる前に、本当に忠実度が必要かどうか検討してください。Unicodeに似ていると、人や古いプログラムを混乱させる可能性があります。たとえば、古いOSはコードページに依存しています。


5

これはStringBuilderIndexOfAny完全な効率を上げるためにを使用し、一括追加するバージョンです。また、重複する文字列を作成するのではなく、元の文字列を返します。

最後に重要なことですが、それには、好きなようにカスタマイズできる似た文字を返すswitchステートメントがあります。Unicode.orgのconfusables lookupをチェックして、フォントに応じてどのようなオプションがあるかを確認してください。

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

これはチェックしない...などの予約名CONには、交換がどうあるべきかは明らかではありませんので。


3

コードを少し掃除してリファクタリングを少し...ストリングタイプの拡張を作成しました。

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

今では使いやすくなっています:

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

「_」以外の文字に置き換える場合は、次のように使用できます。

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

また、置換する文字を追加できます。たとえば、スペースやカンマは必要ありません。

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

それが役に立てば幸い...

乾杯


3

別の簡単な解決策:

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

3

簡単な1行のコード:

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

再利用する場合は、拡張メソッドでラップできます。

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

1

複数の文字を1つにマッピングできないように、衝突を作成できないシステムが必要でした。私は次のようになりました:

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

0

今日、これを行う必要がありました...私の場合、最終的な.kmzファイルの日付と時刻に顧客名を連結する必要がありました。私の最終的な解決策はこれでした:

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

無効な配列にスペース文字を追加すると、スペースを置換することもできます。

多分それは最速ではないかもしれませんが、パフォーマンスは問題ではなかったので、エレガントで理解しやすいと思いました。

乾杯!


-2

これは次のsedコマンドで実行できます。

 sed -e "
 s/[?()\[\]=+<>:;©®”,*|]/_/g
 s/"$'\t'"/ /g
 s/–/-/g
 s/\"/_/g
 s/[[:cntrl:]]/_/g"

より複雑であるが関連する質問もご覧ください。stackoverflow.com
DW

なぜこれをBashではなくC#で行う必要があるのですか?元の質問にC#のタグが表示されましたが、なぜですか?
2016年

1
これを実現するためにインストールされていない可能性のあるC#アプリケーションからBashにシェルアウトしないのはなぜですか。
Peter Ritchie
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.