Rasgele bir dizeden geçerli bir Windows dosya adı nasıl oluşturulur?


97

Dosya adı olarak kullanmak istediğim "Foo: Bar" gibi bir dizem var, ancak Windows'ta ":" karakterine dosya adında izin verilmiyor.

"Foo: Bar" ı "Foo-Bar" gibi bir şeye dönüştürecek bir yöntem var mı?


1
Ben de bugün aynı şeyi yaptım. SO kontrol etmedim herhangi bir nedenle, ama yine de cevabı buldum.
Aaron Smith

Yanıtlar:


155

Bunun gibi bir şey dene:

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

Düzenle:

Yana GetInvalidFileNameChars()10 veya 15 karakter döndürür, bir kullanmak daha iyidir StringBuilderbasit dize yerine; orijinal sürüm daha uzun sürer ve daha fazla bellek tüketir.


1
Dilerseniz bir StringBuilder kullanabilirsiniz, ancak isimler kısaysa ve sanırım buna değmez. Bir karakter [] oluşturmak ve tüm yanlış karakterleri tek bir yinelemede değiştirmek için kendi yönteminizi de oluşturabilirsiniz. İşe yaramadığı sürece basit tutmak her zaman daha iyidir, daha kötü boyunlara sahip olabilirsiniz
Diego Jancic

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
Dizede 2+ farklı geçersiz karakter bulunma olasılığı o kadar küçüktür ki, dizenin performansını önemsemek anlamsızdır.
Serge Wautier

1
Harika bir çözüm, ilginç bir yana, resharper bu Linq sürümünü önerdi: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); Orada olası performans iyileştirmeleri olup olmadığını merak ediyorum. Performans benim en büyük endişem olmadığı için orijinali okunabilirlik amacıyla sakladım. Ancak ilgilenen varsa, kıyaslamaya değer olabilir
chrispepper1989

1
@AndyM Gerek yok. file.name.txt.pdfgeçerli bir pdf'dir. Windows ., uzantı için yalnızca sonuncuyu okur .
Diego Jancic

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

Ancak ":", Windows için tek geçersiz karakter değildir. Ayrıca şunları da halletmeniz gerekecek:

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

Bunlar System.IO.Path.GetInvalidFileNameChars ();

Ayrıca (Windows'ta) "." dosya adındaki tek karakter olamaz (".", "..", "..." ve diğerleri geçersizdir). Dosyaları "." İle adlandırırken dikkatli olun, örneğin:

echo "test" > .test.

".Test" adlı bir dosya oluşturur

Son olarak, işleri gerçekten doğru yapmak istiyorsanız, dikkat etmeniz gereken bazı özel dosya adları vardır . Windows'ta şu adla dosya oluşturamazsınız:

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
Ayrılmış isimleri hiç bilmiyordum. Yine de mantıklı
Greg Dean

4
Ayrıca, değeri ne olursa olsun, bu ayrılmış adlardan biriyle başlayan ve ardından bir ondalık sayı ile başlayan bir dosya adı oluşturamazsınız. ie con.air.avi
John Conrad

".foo" geçerli bir dosya adıdır. "CON" dosya adını bilmiyordum - ne için?
konfigüratör

Kaşı onu. CON konsol içindir.
konfigüratör

Yapılandırıcı teşekkürler; Cevabı güncelledim, haklısınız ".foo" geçerli; ancak ".foo." olası, istenmeyen sonuçlara yol açar. Güncellenmiş.
Phil Price

13

Bu daha verimli değil ama daha eğlenceli :)

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

12

Herhangi birinin optimize edilmiş bir sürüm istediği durumda StringBuilder, bunu kullanın. Rkerer'in hilesini bir seçenek olarak içerir .

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

Güzel ve okunabilir kod için +1. Hataları okumayı ve fark etmeyi çok kolaylaştırır: P .. Bu işlev her zaman orijinal dizgeyi döndürmelidir, çünkü değiştirilen hiçbir zaman gerçek olmayacaktır.
Erti-Chris Eelmaa

Teşekkürler, bence şimdi daha iyi. Onların "Ben yazma birimi testlere zorunda kalmamak gözler tüm hatalar sığ yapmak", açık kaynak ne derler bilirsin ...
Qwertie

8

Diego'nun cevabında ufak bir değişiklik var.

Unicode'dan korkmuyorsanız, geçersiz karakterleri onlara benzeyen geçerli Unicode sembolleriyle değiştirerek biraz daha aslına uygunluk sağlayabilirsiniz. Kereste kesme listelerini içeren yakın tarihli bir projede kullandığım kod:

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

Bu, şu şekilde dosya adları üretir 1⁄2” spruce.txt:1_2_ spruce.txt

Evet, gerçekten işe yarıyor:

Explorer örneği

Caveat Emptor

Bu numaranın NTFS üzerinde çalışacağını biliyordum ama FAT ve FAT32 bölümlerinde de çalıştığını görünce şaşırdım. Çünkü yıllardan bu uzun dosya adları olan Unicode depolanan bile, kadar geriye Windows 95 / NT olarak. Win7, XP ve hatta Linux tabanlı bir yönlendiricide test ettim ve iyi göründüler. Bir DOSBox için aynı şeyi söyleyemeyiz.

Bununla birlikte, bununla delirmeden önce, fazladan sadakate gerçekten ihtiyacınız olup olmadığını düşünün. Unicode benzerleri insanların veya eski programların kafasını karıştırabilir, örneğin eski işletim sistemlerinin kod sayfalarına güvenmesi gibi .


8

Aşağıda, aşağıdakileri Linqkullanan kabul edilen cevabın bir versiyonu bulunmaktadır Enumerable.Aggregate:

string fileName = "something";

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

7

Diego'nun doğru çözümü var ama orada çok küçük bir hata var. Kullanılan string.Replace'in versiyonu string olmalıdır.Değiştir (char, char), string yok.Değiştir (char, string)

Cevabı düzenleyemem, yoksa küçük bir değişiklik yapardım.

Bu yüzden olmalı:

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

5

İşte tam verimlilik için toplu ekleme kullanan StringBuilderve kullanan bir sürüm IndexOfAny. Yinelenen bir dize oluşturmak yerine orijinal dizeyi de döndürür.

Son olarak, istediğiniz gibi özelleştirebileceğiniz benzer karakterleri döndüren bir switch deyimine sahiptir. Check out Unicode.org en confusables arama yazı tipine göre aklınıza gelebilecek seçeneklerin ne olduğunu görmek için.

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

Adları kontrol etmez veya rezerve etmez ., çünkü değiştirmenin ne olması gerektiği net değildir...CON


3

Kodumu biraz temizliyor ve biraz yeniden düzenleme yapıyorum ... string türü için bir uzantı oluşturdum:

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

Şimdi aşağıdakilerle kullanmak daha kolay:

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

"_" Dışında farklı bir karakterle değiştirmek isterseniz şunları kullanabilirsiniz:

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

Ve değiştirmek için karakter ekleyebilirsiniz .. örneğin boşluk veya virgül istemezsiniz:

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

Umarım yardımcı olur...

Şerefe


3

Başka bir basit çözüm:

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

Tek satırlık basit bir kod:

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

Yeniden kullanmak istiyorsanız, bir uzatma yöntemine sarabilirsiniz.

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

1

Çarpışma yaratamayan bir sisteme ihtiyacım vardı, bu yüzden birden fazla karakteri tek bir karaktere eşleyemedim. Şununla bitirdim:

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

Bunu bugün yapmam gerekiyordu ... benim durumumda, son .kmz dosyası için bir müşteri adını tarih ve saatle birleştirmem gerekiyordu. Son çözümüm şuydu:

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

Geçersiz diziye boşluk karakterini eklerseniz, boşlukların yerini bile alabilirsiniz.

Belki en hızlısı değil, ancak performans bir sorun olmadığı için zarif ve anlaşılır buldum.

Şerefe!


-2

Bunu bir sedkomutla yapabilirsiniz:

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

daha karmaşık ama ilgili bir soruya da bakın: stackoverflow.com/questions/4413427/…
DW

Bunun neden Bash yerine C # ile yapılması gerekiyor? Şimdi orijinal soruda bir C # etiketi görüyorum, ama neden?
DW

1
Biliyorum, doğru, neden bunu başarmak için yüklenmemiş olabilecek C # uygulamasından Bash'e kabuk bırakmıyoruz?
Peter Ritchie
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.