C # 살균 파일 이름


174

최근에는 여러 위치에서 여러 MP3를 저장소로 옮겼습니다. ID3 태그 (TankLib-Sharp! 덕분에)를 사용하여 새 파일 이름을 구성하고 있었고 다음과 같은 결과가 나타났습니다 System.NotSupportedException.

"주어진 경로 형식이 지원되지 않습니다."

File.Copy()또는 중 하나에 의해 생성되었습니다 Directory.CreateDirectory().

내 파일 이름을 삭제해야한다는 것을 깨닫는 데 오래 걸리지 않았습니다. 그래서 나는 명백한 일을했다.

public static string SanitizePath_(string path, char replaceChar)
{
    string dir = Path.GetDirectoryName(path);
    foreach (char c in Path.GetInvalidPathChars())
        dir = dir.Replace(c, replaceChar);

    string name = Path.GetFileName(path);
    foreach (char c in Path.GetInvalidFileNameChars())
        name = name.Replace(c, replaceChar);

    return dir + name;
}

놀랍게도 계속 예외가 발생했습니다. Path.GetInvalidPathChars()경로 루트에서 유효하기 때문에 ':'은 세트에 없습니다 . 나는 그것이 의미가 있다고 생각하지만, 이것은 매우 일반적인 문제 여야합니다. 누구나 경로를 위생 처리하는 짧은 코드가 있습니까? 내가 가장 철저하게 생각해 보았지만 아마도 과잉 인 것 같습니다.

    // replaces invalid characters with replaceChar
    public static string SanitizePath(string path, char replaceChar)
    {
        // construct a list of characters that can't show up in filenames.
        // need to do this because ":" is not in InvalidPathChars
        if (_BadChars == null)
        {
            _BadChars = new List<char>(Path.GetInvalidFileNameChars());
            _BadChars.AddRange(Path.GetInvalidPathChars());
            _BadChars = Utility.GetUnique<char>(_BadChars);
        }

        // remove root
        string root = Path.GetPathRoot(path);
        path = path.Remove(0, root.Length);

        // split on the directory separator character. Need to do this
        // because the separator is not valid in a filename.
        List<string> parts = new List<string>(path.Split(new char[]{Path.DirectorySeparatorChar}));

        // check each part to make sure it is valid.
        for (int i = 0; i < parts.Count; i++)
        {
            string part = parts[i];
            foreach (char c in _BadChars)
            {
                part = part.Replace(c, replaceChar);
            }
            parts[i] = part;
        }

        return root + Utility.Join(parts, Path.DirectorySeparatorChar.ToString());
    }

이 기능을 더 빠르고 덜 바로크하게 만들기위한 개선 사항은 대단히 감사하겠습니다.


답변:


314

파일 이름을 정리하려면 다음을 수행하십시오.

private static string MakeValidFileName( string name )
{
   string invalidChars = System.Text.RegularExpressions.Regex.Escape( new string( System.IO.Path.GetInvalidFileNameChars() ) );
   string invalidRegStr = string.Format( @"([{0}]*\.+$)|([{0}]+)", invalidChars );

   return System.Text.RegularExpressions.Regex.Replace( name, invalidRegStr, "_" );
}

3
문제는 파일 이름이 아닌 경로에 관한 것이며 잘못된 문자는 다릅니다.
Dour High Arch

15
어쩌면, 그러나이 코드는 제가 같은 문제를 겪었을 때 확실히 도움이되었습니다. :)
mmr

8
그리고 잠재적으로 훌륭한 SO 사용자가 계속 걸어갑니다 ...이 기능은 훌륭합니다. 감사합니다 Adrevdm ...
Dan Rosenstark

19
좋은 방법입니다. 예약 된 단어가 여전히 당신을 물지 만 머리를 긁적입니다. 출처 : Wikipedia Filename 예약어
Spud

8
마침표는 파일 이름 끝에있는 경우 유효하지 않은 문자이므로 GetInvalidFileNameChars포함하지 않습니다. 그것은 창에서 예외를 던지지 않으며, 단지 그것들을 제거하지만, 마침표가있을 것으로 예상하면 예기치 않은 동작을 일으킬 수 있습니다. .문자열의 끝에있는 경우 유효하지 않은 문자 중 하나로 간주되도록 해당 경우를 처리하도록 정규 표현식을 수정했습니다 .
Scott Chamberlain

120

더 짧은 해결책 :

var invalids = System.IO.Path.GetInvalidFileNameChars();
var newName = String.Join("_", origFileName.Split(invalids, StringSplitOptions.RemoveEmptyEntries) ).TrimEnd('.');

1
@PeterMajeed : 줄 계산이 0에서 시작하는 TIL :-)
Gary McGill

이것은 플랫폼에 따라 다른 문자를 반환 할 수있는 ASP.NET Core의 경우 특히 대답보다 낫습니다.
Alexei

79

Andre의 탁월한 답변을 바탕으로 예약어에 대한 Spud의 의견을 고려 하여이 버전을 만들었습니다.

/// <summary>
/// Strip illegal chars and reserved words from a candidate filename (should not include the directory path)
/// </summary>
/// <remarks>
/// http://stackoverflow.com/questions/309485/c-sharp-sanitize-file-name
/// </remarks>
public static string CoerceValidFileName(string filename)
{
    var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
    var invalidReStr = string.Format(@"[{0}]+", invalidChars);

    var reservedWords = new []
    {
        "CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4",
        "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4",
        "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
    };

    var sanitisedNamePart = Regex.Replace(filename, invalidReStr, "_");
    foreach (var reservedWord in reservedWords)
    {
        var reservedWordPattern = string.Format("^{0}\\.", reservedWord);
        sanitisedNamePart = Regex.Replace(sanitisedNamePart, reservedWordPattern, "_reservedWord_.", RegexOptions.IgnoreCase);
    }

    return sanitisedNamePart;
}

그리고 이것은 내 단위 테스트입니다

[Test]
public void CoerceValidFileName_SimpleValid()
{
    var filename = @"thisIsValid.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual(filename, result);
}

[Test]
public void CoerceValidFileName_SimpleInvalid()
{
    var filename = @"thisIsNotValid\3\\_3.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("thisIsNotValid_3__3.txt", result);
}

[Test]
public void CoerceValidFileName_InvalidExtension()
{
    var filename = @"thisIsNotValid.t\xt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("thisIsNotValid.t_xt", result);
}

[Test]
public void CoerceValidFileName_KeywordInvalid()
{
    var filename = "aUx.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("_reservedWord_.txt", result);
}

[Test]
public void CoerceValidFileName_KeywordValid()
{
    var filename = "auxillary.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("auxillary.txt", result);
}

1
이것은 적어도 질문의 파일 이름 부분에 대한 매우 완전한 대답이며 더 많은 찬사를받을 가치가 있습니다.
Brian MacKay

2
방법이이 방향으로 가고있는 것처럼 보이기 때문에 작은 제안 : this 키워드를 추가하면 편리한 확장 방법이됩니다. 공공 정적 문자열 CoerceValidFileName (이 String 파일 이름)
라이언 맥아더

2
작은 버그 :이 방법은 파일 확장자 (예 :)가없는 예약어를 변경하지 않으며 COM1허용되지 않습니다. 권장되는 수정 사항은 reservedWordPattern을 바꾸고 "^{0}(\\.|$)"대체 문자열을"_reservedWord_$1"
Dehalion


4

System.IO.Path.GetInvalidFileNameChars() 잘못된 문자를 확인 하는 방법을 사용하고 있으며 아무런 문제가 없습니다.

다음 코드를 사용하고 있습니다.

foreach( char invalidchar in System.IO.Path.GetInvalidFileNameChars())
{
    filename = filename.Replace(invalidchar, '_');
}

3

단순히 문자를 밑줄로 바꾸는 것이 아니라 어떤 방식으로 문자를 유지하고 싶었습니다.

내가 생각한 한 가지 방법은 일반 문자로 사용되지 않는 (내 상황에서) 비슷한 모양의 문자로 문자를 바꾸는 것이 었습니다. 그래서 나는 잘못된 문자 목록을 가져 와서 비슷한 것을 발견했습니다.

다음은 유사하게 인코딩하고 디코딩하는 기능입니다.

이 코드에는 모든 System.IO.Path.GetInvalidFileNameChars () 문자에 대한 전체 목록이 포함되어 있지 않습니다. 따라서 나머지 문자를 밑줄로 바꾸거나 확장하는 것은 사용자의 책임입니다.

private static Dictionary<string, string> EncodeMapping()
{
    //-- Following characters are invalid for windows file and folder names.
    //-- \/:*?"<>|
    Dictionary<string, string> dic = new Dictionary<string, string>();
    dic.Add(@"\", "Ì"); // U+OOCC
    dic.Add("/", "Í"); // U+OOCD
    dic.Add(":", "¦"); // U+00A6
    dic.Add("*", "¤"); // U+00A4
    dic.Add("?", "¿"); // U+00BF
    dic.Add(@"""", "ˮ"); // U+02EE
    dic.Add("<", "«"); // U+00AB
    dic.Add(">", "»"); // U+00BB
    dic.Add("|", "│"); // U+2502
    return dic;
}

public static string Escape(string name)
{
    foreach (KeyValuePair<string, string> replace in EncodeMapping())
    {
        name = name.Replace(replace.Key, replace.Value);
    }

    //-- handle dot at the end
    if (name.EndsWith(".")) name = name.CropRight(1) + "°";

    return name;
}

public static string UnEscape(string name)
{
    foreach (KeyValuePair<string, string> replace in EncodeMapping())
    {
        name = name.Replace(replace.Value, replace.Key);
    }

    //-- handle dot at the end
    if (name.EndsWith("°")) name = name.CropRight(1) + ".";

    return name;
}

자신 만의 모양을 선택할 수 있습니다. 창에서 문자표 앱을 사용하여 광산을 선택했습니다.%windir%\system32\charmap.exe

검색을 통해 조정하면이 코드를 업데이트합니다.


fullwidth 형식 !"#$%&'()*+,-./:;<=>?@{|}~ 이나 /문제없이 파일 이름에 직접 사용할 수있는 SOLIDUS 및`⁄`FRACTION SLASH와 같은 다른 형식의 문자와 유사한 문자가 많이 있습니다.
phuclv

2

문제는 먼저 Path.GetDirectoryName나쁜 문자열을 호출한다는 것 입니다. 파일 이름이 아닌 문자가 있으면 .Net은 문자열의 어느 부분이 디렉토리인지 throw인지 알 수 없습니다. 문자열 비교를 수행해야합니다.

전체 경로가 아닌 파일 이름 만 나쁜 것으로 가정하면 다음을 시도하십시오.

public static string SanitizePath(string path, char replaceChar)
{
    int filenamePos = path.LastIndexOf(Path.DirectorySeparatorChar) + 1;
    var sb = new System.Text.StringBuilder();
    sb.Append(path.Substring(0, filenamePos));
    for (int i = filenamePos; i < path.Length; i++)
    {
        char filenameChar = path[i];
        foreach (char c in Path.GetInvalidFileNameChars())
            if (filenameChar.Equals(c))
            {
                filenameChar = replaceChar;
                break;
            }

        sb.Append(filenameChar);
    }

    return sb.ToString();
}

2

나는 과거에 이것으로 성공했습니다.

좋고 짧고 정적 :-)

    public static string returnSafeString(string s)
    {
        foreach (char character in Path.GetInvalidFileNameChars())
        {
            s = s.Replace(character.ToString(),string.Empty);
        }

        foreach (char character in Path.GetInvalidPathChars())
        {
            s = s.Replace(character.ToString(), string.Empty);
        }

        return (s);
    }

2

여기에는 많은 작업 솔루션이 있습니다. 완전성을 기하기 위해 정규 표현식을 사용하지 않고 LINQ를 사용하는 접근 방식이 있습니다.

var invalids = Path.GetInvalidFileNameChars();
filename = invalids.Aggregate(filename, (current, c) => current.Replace(c, '_'));

또한 매우 짧은 솔루션입니다.)


1
나는 한 라이너를 좋아합니다 :)
Larry

1

Andre의 코드를 기반으로 한 효율적인 지연 로딩 확장 방법은 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LT
{
    public static class Utility
    {
        static string invalidRegStr;

        public static string MakeValidFileName(this string name)
        {
            if (invalidRegStr == null)
            {
                var invalidChars = System.Text.RegularExpressions.Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()));
                invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidChars);
            }

            return System.Text.RegularExpressions.Regex.Replace(name, invalidRegStr, "_");
        }
    }
}

0

디렉토리와 파일 이름을 함께 추가하고 파일을 독립적으로 삭제하지 않고 삭제하면 코드가 더 깨끗해집니다. :을 삭제하는 경우 문자열에서 두 번째 문자를 가져 가십시오. "replacechar"와 같으면 콜론으로 바꾸십시오. 이 응용 프로그램은 귀하가 사용하기 때문에 그러한 솔루션은 완벽하게 충분해야합니다.


-1
using System;
using System.IO;
using System.Linq;
using System.Text;

public class Program
{
    public static void Main()
    {
        try
        {
            var badString = "ABC\\DEF/GHI<JKL>MNO:PQR\"STU\tVWX|YZA*BCD?EFG";
            Console.WriteLine(badString);
            Console.WriteLine(SanitizeFileName(badString, '.'));
            Console.WriteLine(SanitizeFileName(badString));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }

    private static string SanitizeFileName(string fileName, char? replacement = null)
    {
        if (fileName == null) { return null; }
        if (fileName.Length == 0) { return ""; }

        var sb = new StringBuilder();
        var badChars = Path.GetInvalidFileNameChars().ToList();

        foreach (var @char in fileName)
        {
            if (badChars.Contains(@char)) 
            {
                if (replacement.HasValue)
                {
                    sb.Append(replacement.Value);
                }
                continue; 
            }
            sb.Append(@char);
        }
        return sb.ToString();
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.