Como fazer um nome de arquivo válido Windows a partir de uma cadeia arbitrária?

StackOverflow https://stackoverflow.com/questions/620605

  •  05-07-2019
  •  | 
  •  

Pergunta

Eu tenho uma string como "Foo Bar" que eu quero usar como um nome de arquivo, mas no Windows a ":". Caractere não é permitido em um nome de arquivo

Existe um método que irá transformar "Foo Bar"? Em algo como "foo- Bar"

Foi útil?

Solução

Tente algo parecido com isto:

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

Editar:

Desde GetInvalidFileNameChars() retornará 10 ou 15 caracteres, é melhor usar um StringBuilder vez de uma cadeia simples; a versão original vai demorar mais tempo e consome mais memória.

Outras dicas

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

No entanto ":" não é o personagem só ilegal para Windows. Você também terá que lidar com:

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

Estes estão contidos em System.IO.Path.GetInvalidFileNameChars ();

Além disso (no Windows), "" não pode ser o único personagem no nome do arquivo (tanto " '' .. '' ...", e assim por diante são inválidos). Tenha cuidado ao nomear arquivos com, por exemplo "":

echo "test" > .test.

Irá gerar um arquivo chamado ".test"

Por fim, se você realmente quer fazer as coisas corretamente, há alguns nomes de arquivo especial você precisa olhar para fora. No Windows você não pode criar arquivos com o nome:

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.

Isto não é mais eficiente, mas é mais divertido:)

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

Caso alguém queira uma versão otimizada baseada em StringBuilder, use este. Inclui truque de rkagerer como uma opção.

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

Diego tem a solução correta, mas há um muito pequeno erro lá. A versão do String.Replace sendo usado deve ser String.Replace (char, char), não há uma String.Replace (char, string)

Eu não pode editar a resposta ou eu teria feito apenas a pequena alteração.

Por isso, deve ser:

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

Aqui está uma leve torção na resposta de Diego.

Se você não tem medo de Unicode, você pode manter um pouco mais fidelidade, substituindo os caracteres inválidos com símbolos Unicode válidos que eles se assemelham. Aqui está o código que usei em um projeto recente envolvendo cutlists madeira:

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

Isso produz nomes de arquivos como 1⁄2” spruce.txt vez de 1_2_ spruce.txt

Sim, ele realmente funciona:

amostra Explorer

advertência Emptor

Eu sabia que este truque iria trabalhar em NTFS, mas foi surpreendido ao encontrá-lo também funciona em FAT e FAT32 partições. Isso porque nomes de arquivos longos são armazenado em Unicode , mesmo tanto para trás como Windows 95 / NT. I testado em Win7, XP, e até mesmo um roteador baseado em Linux e eles mostraram-se OK. Não posso dizer o mesmo para dentro de um DOSBox.

Dito isto, antes de enlouquecer com isso, considerar se você realmente precisa da fidelidade extra. Os Unicode sósias poderia confundir as pessoas ou programas antigos, por exemplo, de OS mais velhos contando com codepages .

Aqui está uma versão da resposta aceita usando Linq que usos Enumerable.Aggregate :

string fileName = "something";

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

Aqui está uma versão que usa StringBuilder e IndexOfAny com acréscimo em massa para a plena eficiência. Ele também retorna a string original, em vez de criar uma cadeia duplicada.

Por último, mas não menos importante, tem uma instrução switch que retorna sósia caracteres que você pode personalizar qualquer maneira que você desejar. Confira de Unicode.org confusables pesquisa para ver quais as opções que você pode ter, dependendo da fonte .

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

Ele não verifica ., .., ou nomes reservados como CON porque não é claro o que a substituição deve ser.

Outra solução simples:

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

Limpeza de um pouco de meu código e fazer um pouco de refactoring ... Eu criei uma extensão para o tipo string:

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

Agora é mais fácil de usar com:

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

Se você deseja substituir por um caractere diferente de "_" você pode usar:

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

E você pode adicionar caracteres para substituir .. por exemplo, você não quer espaços ou vírgulas:

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

Hope isso ajuda ...

Felicidades

Eu precisava fazer isso hoje ... no meu caso, eu precisava para concatenar um nome de cliente com a data e hora de um arquivo .kmz final. Minha solução final foi a seguinte:

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

Você pode até mesmo torná-lo substituir os espaços se você adicionar o caractere espaço para a matriz inválido.

Talvez não seja o mais rápido, mas como o desempenho não era um problema, eu achei elegante e compreensível.

Felicidades!

Você pode fazer isso com um comando sed:

 sed -e "
 s/[?()\[\]=+<>:;©®”,*|]/_/g
 s/"$'\t'"/ /g
 s/–/-/g
 s/\"/_/g
 s/[[:cntrl:]]/_/g"
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top