임의의 문자열에서 유효한 Windows 파일 이름을 만드는 방법은 무엇입니까?


97

파일 이름으로 사용하려는 "Foo : Bar"와 같은 문자열이 있지만 Windows에서는 파일 이름에 ":"문자를 사용할 수 없습니다.

"Foo : Bar"를 "Foo- Bar"와 같은 것으로 바꾸는 방법이 있습니까?


1
오늘도 똑같은 일을했습니다. 나는 어떤 이유로 그렇게 확인하지 않았지만 어쨌든 답을 찾았습니다.
Aaron Smith

답변:


154

다음과 같이 시도하십시오.

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

편집하다:

GetInvalidFileNameChars()10 개 또는 15 개의 문자를 반환 하므로 StringBuilder간단한 문자열 대신 a를 사용하는 것이 좋습니다. 원래 버전은 더 오래 걸리고 더 많은 메모리를 소비합니다.


1
원하는 경우 StringBuilder를 사용할 수 있지만 이름이 짧고 그만한 가치가 없다고 생각합니다. char []를 만들고 한 번의 반복에서 모든 잘못된 문자를 대체하는 고유 한 메서드를 만들 수도 있습니다. 작동하지 않는 한 항상 단순하게 유지하는 것이 좋습니다. 병목이 더 나빠질 수 있습니다
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
문자열에 2 개 이상의 다른 유효하지 않은 문자가있을 확률은 너무 작아서 string.Replace ()의 성능에 대한 관심은 무의미합니다.
Serge Wautier 2011 년

1
흥미로운 솔루션이지만 resharper는이 Linq 버전을 제안했습니다. fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); 가능한 성능 향상이 있는지 궁금합니다. 나는 성능이 가장 큰 관심사가 아니기 때문에 가독성을 위해 원본을 보관했습니다. 그러나 관심이 있다면 벤치마킹 할 가치가있을 것입니다
chrispepper1989

1
@AndyM 필요 없습니다. file.name.txt.pdf유효한 pdf입니다. Windows .는 확장에 대해 마지막 만 읽습니다 .
Diego Jancic

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
예약 된 이름에 대해 전혀 몰랐습니다. 그래도 말이 돼요
Greg Dean

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 ..이 함수는 항상 원래의 문자열을 반환해야합니다. 변경된 것은 결코 사실이 아닙니다.
Erti-Chris Eelmaa

고마워, 이제 더 나아진 것 같아. 당신은 그들이 "나는 쓰기 단위 테스트를하지 않아도 많은 눈이 모든 버그 얕은을"오픈 소스에 대해 뭐라고하는지 알아요 ...
Qwertie

8

다음을 사용 Linq하는 허용되는 답변의 버전은 다음과 같습니다 Enumerable.Aggregate.

string fileName = "something";

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

7

Diego는 올바른 해결책을 가지고 있지만 거기에 아주 작은 실수가 하나 있습니다. 사용중인 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

Diego의 대답에 약간의 비틀기가 있습니다.

유니 코드를 두려워하지 않는 경우 유효하지 않은 문자를 유사한 유효한 유니 코드 기호로 대체하여 좀 더 충실도를 유지할 수 있습니다. 다음은 목재 절단 목록과 관련된 최근 프로젝트에서 사용한 코드입니다.

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

예, 실제로 작동합니다.

Explorer 샘플

경고 Emptor

이 트릭이 NTFS에서 작동한다는 것을 알고 있었지만 FAT 및 FAT32 파티션에서도 작동한다는 사실에 놀랐습니다. 때문에의 그 긴 파일 이름이 되는 유니 코드로 저장 심지어 까지 다시 윈도우 95 / NT 등. 나는 Win7, XP, 심지어 Linux 기반 라우터에서도 테스트를했는데 괜찮았다. DOSBox 내부에서도 똑같이 말할 수 없습니다.

즉, 당신이 이것에 열광하기 전에 추가 충실도가 정말로 필요한지 고려하십시오. 유니 코드와 유사한 모양은 사람이나 오래된 프로그램을 혼동 할 수 있습니다. 예를 들어 코드 페이지에 의존하는 오래된 OS가 있습니다.


5

여기 버전의 사용 StringBuilderIndexOfAny전체 효율을 대량 APPEND와. 또한 중복 문자열을 생성하는 대신 원래 문자열을 반환합니다.

마지막으로, 원하는 방식으로 사용자 정의 할 수있는 유사 문자를 반환하는 switch 문이 있습니다. 체크 아웃 Unicode.org의 confusables가 조회 글꼴에 따라, 당신이있을 어떤 옵션을 볼 수 있습니다.

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

간단한 한 줄 코드 :

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

충돌을 만들 수없는 시스템이 필요했기 때문에 여러 캐릭터를 하나로 매핑 할 수 없었습니다. 나는 결국 :

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/questions/4413427/…
DW

Bash가 아닌 C #으로이 작업을 수행해야하는 이유는 무엇입니까? 이제 원래 질문에 C # 태그가 표시되지만 그 이유는 무엇입니까?
DW

1
맞습니다. C # 응용 프로그램에서이 작업을 수행하기 위해 설치되지 않은 Bash로 셸 아웃하는 것이 어떻습니까?
Peter Ritchie
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.