2012-09-16 13:59:11 +0000 2012-09-16 13:59:11 +0000
81
81

Como é que o comando RENAME do Windows interpreta wildcards?

Como é que o comando Windows RENAME (REN) interpreta wildcards?

A instalação de AJUDA construída não ajuda nada - não trata de wildcards de forma alguma.

O Microsoft technet XP ajuda online não é muito melhor. Aqui está tudo o que tem a dizer sobre os wildcards:

“Pode usar wildcards (* e ?) em qualquer um dos parâmetros de nome de ficheiro. Se utilizar wildcards no nome do ficheiro2, os caracteres representados pelos wildcards serão idênticos aos caracteres correspondentes no nome do ficheiro1”

& Pouca ajuda - há muitas maneiras de interpretar essa afirmação.

Consegui utilizar com sucesso wildcards no parâmetro filename2 em algumas ocasiões, mas tem sido sempre tentativa e erro. Não tenho sido capaz de antecipar o que funciona e o que não funciona. Frequentemente tive de recorrer à escrita de um pequeno guião de lote com um loop FOR que analisa cada nome para que eu possa construir cada novo nome conforme as necessidades. Não é muito conveniente.

Se eu soubesse as regras de como os wildcards são processados, então imagino que poderia usar o comando RENAME de forma mais eficaz sem ter de recorrer ao lote com a mesma frequência. É claro que conhecer as regras também beneficiaria o desenvolvimento de lotes.

(Sim - este é um caso em que estou a colocar uma pergunta e resposta emparelhada. Cansei-me de não conhecer as regras e decidi experimentar por conta própria. Imagino que muitos outros possam estar interessados no que descobri)

Respostas (4)

120
120
120
2012-09-16 14:00:21 +0000

Estas regras foram descobertas após extensos testes numa máquina Vista. Nenhum teste foi feito com unicode em nomes de ficheiro.

RENAME requer 2 parâmetros - uma máscara de origem, seguida por uma máscara de destino. Tanto a sourceMask como a targetMask podem conter * e/ou ? wildcards. O comportamento dos wildcards muda ligeiramente entre as máscaras-fonte e as máscaras-alvo.

Nota - REN pode ser utilizada para renomear uma pasta, mas os wildcards são não* permitidos na MáscaraFonte ou na Máscara de destino quando se renomeia uma pasta. Se a sourceMask corresponder a pelo menos um ficheiro, então o(s) ficheiro(s) será(ão) renomeado(s) e as pastas serão ignoradas. Se a MáscaraFonte corresponder apenas a pastas e não a ficheiros, então será gerado um erro de sintaxe se aparecerem wildcards na fonte ou no alvo. Se a MáscaraFonte não corresponder a nada, então resulta um erro “ficheiro não encontrado”.

Também, ao renomear ficheiros, os wildcards só são permitidos na parte do nome do ficheiro da MáscaraFonte. Os wildcards não são permitidos no caminho que conduz ao nome do ficheiro.

sourceMask

A sourceMask funciona como um filtro para determinar que ficheiros são renomeados. Os wildcards funcionam aqui como com qualquer outro comando que filtra os nomes dos ficheiros.

  • ? - Corresponde a qualquer 0 ou 1 caracter exceto . Este wildcard é ganancioso - consome sempre o próximo caracter se não for um . No entanto, não corresponde a nada sem falha se no fim do nome ou se o próximo caractere for um .
  • * - Corresponde a qualquer 0 ou mais caracteres incluindo . (com uma excepção abaixo). Este wildcard não é ganancioso. Combina tão pouco ou tanto quanto for necessário para permitir a correspondência de caracteres subsequentes.

Todos os caracteres que não sejam wildcard devem corresponder a si mesmos, com algumas excepções de casos especiais.

  • . - Corresponde a si próprio ou pode corresponder ao fim do nome (nada) se não restarem mais caracteres. (Nota - um nome válido do Windows não pode terminar com .)

  • {space}* - Corresponde a si próprio ou pode corresponder ao fim do nome (nada), se não restarem mais caracteres. (Nota - um nome válido do Windows não pode terminar com {space})

  • *.* no fim - Combina qualquer 0 ou mais caracteres exceto . A terminação . pode realmente ser qualquer combinação de . e {space} desde que o último caractere na máscara seja . Esta é a única e única excepção onde * não combina simplesmente com qualquer conjunto de caracteres.

As regras acima não são assim tão complexas. Mas há mais uma regra muito importante que torna a situação confusa: A sourceMask é comparada tanto com o nome longo como com o nome curto 8.3 (se existir). Esta última regra pode tornar a interpretação dos resultados muito complicada, porque nem sempre é óbvia quando a máscara é comparada através do nome curto.

É possível utilizar RegEdit para desactivar a geração de nomes curtos de 8,3 nomes em volumes NTFS, altura em que a interpretação dos resultados da máscara de arquivo é muito mais directa. Qualquer nome curto que tenha sido gerado antes de desactivar nomes curtos permanecerá.

targetMask

Note - Não fiz nenhum teste rigoroso, mas parece que estas mesmas regras também funcionam para o nome alvo do comando COPY

A targetMask especifica o novo nome. É sempre aplicada ao nome longo completo; A targetMask nunca é aplicada ao nome curto 8.3, mesmo que a sourceMask corresponda ao nome curto 8.3.

A presença ou ausência de wildcards na máscara de origem não tem impacto na forma como os wildcards são processados na Máscara de destino.

Na discussão seguinte - c representa qualquer carácter que não seja *, ?, ou .

A Máscara da fonte é processada contra o nome da fonte estritamente da esquerda para a direita, sem retrocesso.

  • c - Avança a posição dentro do nome da fonte apenas se o carácter da fonte não for ., e acrescenta sempre c ao nome do alvo. (Substitui o caractere que estava na fonte por c, mas nunca substitui .)

  • ?* - Faz corresponder o próximo caractere do nome longo da fonte e anexa-o ao nome alvo desde que o caractere da fonte não seja . Se o próximo caractere for . ou se no fim do nome da fonte, então nenhum caractere é adicionado ao resultado e a posição actual dentro do nome da fonte é inalterada.

  • * no fim da Máscara de destino - Acrescenta todos os caracteres restantes da fonte ao alvo. Se já estiver no fim da fonte, então não faz nada.

& - *c* - Junta todos os caracteres da posição actual até à última ocorrência de c& (correspondência gananciosa sensível a maiúsculas e minúsculas) e anexa o conjunto de caracteres correspondentes ao nome do alvo. Se c não for encontrado, então todos os caracteres restantes da fonte são anexados, seguidos de c Esta é a única situação que conheço em que a correspondência de padrão de ficheiro do Windows é sensível a maiúsculas e minúsculas.

  • *. - Combina todos os caracteres da posição actual através da última ocasião de . (correspondência gananciosa) e anexa o conjunto combinado de caracteres para o nome do alvo. Se . não for encontrado, então todos os caracteres restantes da fonte são anexados, seguidos de .

  • *?* - anexa todos os caracteres restantes da fonte ao alvo. Se já estiver no fim da fonte, então nada faz.

  • .* sem * na frente - Avança a posição na fonte através da primeira ocorrência de . sem copiar quaisquer caracteres, e anexa . ao nome do alvo. Se . não for encontrado na fonte, então avança para o fim da fonte e anexa . ao nome do alvo.

após a Máscara alvo ter sido esgotada, Qualquer trailing . e {space} são aparados no final do nome do alvo resultante porque os nomes dos ficheiros Windows não podem terminar com . ou {space}

Alguns exemplos práticos

Substituir um caractere na 1ª e 3ª posições antes de qualquer extensão (adiciona um 2º ou 3º caracter se ainda não existir)

ren * A?Z*
  1 -> AZ
  12 -> A2Z
  1.txt -> AZ.txt
  12.txt -> A2Z.txt
  123 -> A2Z
  123.txt -> A2Z.txt
  1234 -> A2Z4
  1234.txt -> A2Z4.txt

Alterar a extensão (final) de cada ficheiro

ren * *.txt
  a -> a.txt
  b.dat -> b.txt
  c.x.y -> c.x.txt

Anexar uma extensão a cada ficheiro

ren * *?.bak
  a -> a.bak
  b.dat -> b.dat.bak
  c.x.y -> c.x.y.bak

Remover qualquer extensão extra após a extensão inicial. Note-se que ? adequado deve ser usado para preservar o nome completo existente e a extensão inicial.

ren * ?????.?????
  a -> a
  a.b -> a.b
  a.b.c -> a.b
  part1.part2.part3 -> part1.part2
  123456.123456.123456 -> 12345.12345 (note truncated name and extension because not enough `?` were used)

O mesmo que acima, mas filtrar os ficheiros com nome inicial e/ou extensão superior a 5 caracteres, para que não sejam truncados. (Obviamente poderia adicionar um ? adicional em cada extremidade da targetMask para preservar nomes e extensões até 6 caracteres)

ren ?????.?????.* ?????.?????
  a -> a
  a.b -> a.b
  a.b.c -> a.b
  part1.part2.part3 -> part1.part2
  123456.123456.123456 (Not renamed because doesn't match sourceMask)

Alterar caracteres após a última _ no nome e tentar preservar a extensão. (Não funciona correctamente se _ aparecer na extensão)

ren *_* *_NEW.*
  abcd_12345.txt -> abcd_NEW.txt
  abc_newt_1.dat -> abc_newt_NEW.dat
  abcdef.jpg (Not renamed because doesn't match sourceMask)
  abcd_123.a_b -> abcd_123.a_NEW (not desired, but no simple RENAME form will work in this case)

Qualquer nome pode ser dividido em componentes que são delimitados por . Os caracteres só podem ser anexados ou eliminados do final de cada componente. Os caracteres não podem ser apagados ou adicionados ao início ou meio de um componente, preservando o resto com wildcards. As substituições são permitidas em qualquer lugar.

ren ??????.??????.?????? ?x.????999.*rForTheCourse
  part1.part2 -> px.part999.rForTheCourse
  part1.part2.part3 -> px.part999.parForTheCourse
  part1.part2.part3.part4 (Not renamed because doesn't match sourceMask)
  a.b.c -> ax.b999.crForTheCourse
  a.b.CarPart3BEER -> ax.b999.CarParForTheCourse

Se os nomes curtos estiverem activados, então uma Máscara Fonte com pelo menos 8 ? para o nome e pelo menos 3 ? para a extensão irá corresponder a todos os ficheiros porque irá sempre corresponder ao nome curto 8,3.

ren ????????.??? ?x.????999.*rForTheCourse
  part1.part2.part3.part4 -> px.part999.part3.parForTheCourse

Quirk/bug? útil para apagar prefixos de nomes

Este post SuperUser descreve como um conjunto de barras de avanço (/) pode ser usado para apagar caracteres principais de um nome de ficheiro. É necessária uma barra para cada caracter a ser eliminado. Confirmei o comportamento numa máquina Windows 10.

ren "abc-*.txt" "////*.txt"
  abc-123.txt --> 123.txt
  abc-HelloWorld.txt --> HelloWorld.txt

Esta técnica só funciona se tanto a máscara de origem como a de destino estiverem entre aspas duplas. Todas as seguintes formas sem as aspas necessárias falham com este erro: The syntax of the command is incorrect

REM - All of these forms fail with a syntax error.
ren abc-*.txt "////*.txt"
ren "abc-*.txt" ////*.txt
ren abc-*.txt ////*.txt

A / não pode ser utilizada para remover quaisquer caracteres no meio ou no fim de um nome de ficheiro. Só pode remover os caracteres principais (prefixo). Note-se também que esta técnica não funciona com nomes de pastas.

Tecnicamente o / não funciona como um wildcard. Em vez disso, está a fazer uma simples substituição de caracteres, mas depois da substituição, o comando REN reconhece que / não é válido num nome de ficheiro, e retira o / inicial do nome. REN dá um erro de sintaxe se detectar / no meio de um nome de destino.

Possível erro RENAME - um único comando pode renomear o mesmo ficheiro duas vezes!

Começando numa pasta de teste vazia:

C:\test>copy nul 123456789.123
        1 file(s) copied.

C:\test>dir /x
 Volume in drive C is OS
 Volume Serial Number is EE2C-5A11

 Directory of C:\test

09/15/2012 07:42 PM <DIR> .
09/15/2012 07:42 PM <DIR> ..
09/15/2012 07:42 PM 0 123456~1.123 123456789.123
               1 File(s) 0 bytes
               2 Dir(s) 327,237,562,368 bytes free

C:\test>ren *1* 2*3.?x

C:\test>dir /x
 Volume in drive C is OS
 Volume Serial Number is EE2C-5A11

 Directory of C:\test

09/15/2012 07:42 PM <DIR> .
09/15/2012 07:42 PM <DIR> ..
09/15/2012 07:42 PM 0 223456~1.XX 223456789.123.xx
               1 File(s) 0 bytes
               2 Dir(s) 327,237,562,368 bytes free

REM Expected result = 223456789.123.x

Acredito que a Máscara Fonte *1* corresponde primeiro ao nome longo do ficheiro, e o ficheiro é renomeado para o resultado esperado de 223456789.123.x. RENAME continua então a procurar mais ficheiros para processar e encontra o ficheiro recentemente nomeado através do novo nome curto de 223456~1.X. O ficheiro é então renomeado de novo, dando o resultado final de 223456789.123.xx.

Se eu desactivar a geração de 8,3 nomes, então a RENAME dá o resultado esperado.

Ainda não resolvi todas as condições de desencadeamento que devem existir para induzir este comportamento estranho. Estava preocupado com a possibilidade de ser possível criar um RENAME recorrente sem fim, mas nunca fui capaz de induzir um.

Creio que tudo o que se segue deve ser verdade para induzir o insecto. Todos os casos que eu vi tinham as seguintes condições, mas nem todos os casos que preenchiam as seguintes condições tinham sido submetidos a uma escuta.

  • Os nomes curtos 8,3 devem ser activados
  • A máscara de origem deve corresponder ao nome longo original. & - A renomeação inicial tem de gerar um nome curto que também coincida com o nome curto original da Máscara Fonte
  • A renomeação inicial tem de ser ordenada mais tarde do que o nome curto original (se existisse?)
4
4
4
2014-12-16 10:13:11 +0000

Semelhante ao exebook, aqui está uma implementação C# para obter o nome do ficheiro alvo a partir de um ficheiro fonte.

encontrei 1 pequeno erro nos exemplos de dbenham:

ren *_* *_NEW.*
   abc_newt_1.dat -> abc_newt_NEW.txt (should be: abd_newt_NEW.dat)

Aqui está o código:

/// <summary>
    /// Returns a filename based on the sourcefile and the targetMask, as used in the second argument in rename/copy operations.
    /// targetMask may contain wildcards (* and ?).
    /// 
    /// This follows the rules of: http://superuser.com/questions/475874/how-does-the-windows-rename-command-interpret-wildcards
    /// </summary>
    /// <param name="sourcefile">filename to change to target without wildcards</param>
    /// <param name="targetMask">mask with wildcards</param>
    /// <returns>a valid target filename given sourcefile and targetMask</returns>
    public static string GetTargetFileName(string sourcefile, string targetMask)
    {
        if (string.IsNullOrEmpty(sourcefile))
            throw new ArgumentNullException("sourcefile");

        if (string.IsNullOrEmpty(targetMask))
            throw new ArgumentNullException("targetMask");

        if (sourcefile.Contains('*') || sourcefile.Contains('?'))
            throw new ArgumentException("sourcefile cannot contain wildcards");

        // no wildcards: return complete mask as file
        if (!targetMask.Contains('*') && !targetMask.Contains('?'))
            return targetMask;

        var maskReader = new StringReader(targetMask);
        var sourceReader = new StringReader(sourcefile);
        var targetBuilder = new StringBuilder();

        while (maskReader.Peek() != -1)
        {

            int current = maskReader.Read();
            int sourcePeek = sourceReader.Peek();
            switch (current)
            {
                case '*':
                    int next = maskReader.Read();
                    switch (next)
                    {
                        case -1:
                        case '?':
                            // Append all remaining characters from sourcefile
                            targetBuilder.Append(sourceReader.ReadToEnd());
                            break;
                        default:
                            // Read source until the last occurrance of 'next'.
                            // We cannot seek in the StringReader, so we will create a new StringReader if needed
                            string sourceTail = sourceReader.ReadToEnd();
                            int lastIndexOf = sourceTail.LastIndexOf((char) next);
                            // If not found, append everything and the 'next' char
                            if (lastIndexOf == -1)
                            {
                                targetBuilder.Append(sourceTail);
                                targetBuilder.Append((char) next);

                            }
                            else
                            {
                                string toAppend = sourceTail.Substring(0, lastIndexOf + 1);
                                string rest = sourceTail.Substring(lastIndexOf + 1);
                                sourceReader.Dispose();
                                // go on with the rest...
                                sourceReader = new StringReader(rest);
                                targetBuilder.Append(toAppend);
                            }
                            break;
                    }

                    break;
                case '?':
                    if (sourcePeek != -1 && sourcePeek != '.')
                    {
                        targetBuilder.Append((char)sourceReader.Read());
                    }
                    break;
                case '.':
                    // eat all characters until the dot is found
                    while (sourcePeek != -1 && sourcePeek != '.')
                    {
                        sourceReader.Read();
                        sourcePeek = sourceReader.Peek();
                    }

                    targetBuilder.Append('.');
                    // need to eat the . when we peeked it
                    if (sourcePeek == '.')
                        sourceReader.Read();

                    break;
                default:
                    if (sourcePeek != '.') sourceReader.Read(); // also consume the source's char if not .
                    targetBuilder.Append((char)current);
                    break;
            }

        }

        sourceReader.Dispose();
        maskReader.Dispose();
        return targetBuilder.ToString().TrimEnd('.', ' ');
    }

E aqui está um método de teste NUnit para testar os exemplos:

[Test]
    public void TestGetTargetFileName()
    {
        string targetMask = "?????.?????";
        Assert.AreEqual("a", FileUtil.GetTargetFileName("a", targetMask));
        Assert.AreEqual("a.b", FileUtil.GetTargetFileName("a.b", targetMask));
        Assert.AreEqual("a.b", FileUtil.GetTargetFileName("a.b.c", targetMask));
        Assert.AreEqual("part1.part2", FileUtil.GetTargetFileName("part1.part2.part3", targetMask));
        Assert.AreEqual("12345.12345", FileUtil.GetTargetFileName("123456.123456.123456", targetMask));

        targetMask = "A?Z*";
        Assert.AreEqual("AZ", FileUtil.GetTargetFileName("1", targetMask));
        Assert.AreEqual("A2Z", FileUtil.GetTargetFileName("12", targetMask));
        Assert.AreEqual("AZ.txt", FileUtil.GetTargetFileName("1.txt", targetMask));
        Assert.AreEqual("A2Z.txt", FileUtil.GetTargetFileName("12.txt", targetMask));
        Assert.AreEqual("A2Z", FileUtil.GetTargetFileName("123", targetMask));
        Assert.AreEqual("A2Z.txt", FileUtil.GetTargetFileName("123.txt", targetMask));
        Assert.AreEqual("A2Z4", FileUtil.GetTargetFileName("1234", targetMask));
        Assert.AreEqual("A2Z4.txt", FileUtil.GetTargetFileName("1234.txt", targetMask));

        targetMask = "*.txt";
        Assert.AreEqual("a.txt", FileUtil.GetTargetFileName("a", targetMask));
        Assert.AreEqual("b.txt", FileUtil.GetTargetFileName("b.dat", targetMask));
        Assert.AreEqual("c.x.txt", FileUtil.GetTargetFileName("c.x.y", targetMask));

        targetMask = "*?.bak";
        Assert.AreEqual("a.bak", FileUtil.GetTargetFileName("a", targetMask));
        Assert.AreEqual("b.dat.bak", FileUtil.GetTargetFileName("b.dat", targetMask));
        Assert.AreEqual("c.x.y.bak", FileUtil.GetTargetFileName("c.x.y", targetMask));

        targetMask = "*_NEW.*";
        Assert.AreEqual("abcd_NEW.txt", FileUtil.GetTargetFileName("abcd_12345.txt", targetMask));
        Assert.AreEqual("abc_newt_NEW.dat", FileUtil.GetTargetFileName("abc_newt_1.dat", targetMask));
        Assert.AreEqual("abcd_123.a_NEW", FileUtil.GetTargetFileName("abcd_123.a_b", targetMask));

        targetMask = "?x.????999.*rForTheCourse";

        Assert.AreEqual("px.part999.rForTheCourse", FileUtil.GetTargetFileName("part1.part2", targetMask));
        Assert.AreEqual("px.part999.parForTheCourse", FileUtil.GetTargetFileName("part1.part2.part3", targetMask));
        Assert.AreEqual("ax.b999.crForTheCourse", FileUtil.GetTargetFileName("a.b.c", targetMask));
        Assert.AreEqual("ax.b999.CarParForTheCourse", FileUtil.GetTargetFileName("a.b.CarPart3BEER", targetMask));

    }
1
1
1
2014-04-09 17:07:37 +0000

Talvez alguém possa achar isto útil. Este código JavaScript é baseado na resposta dada por dbenham acima.

Não testei sourceMask muito, mas targetMask corresponde a todos os exemplos dados pela dbenham.

function maskMatch(path, mask) {
    mask = mask.replace(/\./g, '\.')
    mask = mask.replace(/\?/g, '.')
    mask = mask.replace(/\*/g, '.+?')
    var r = new RegExp('^'+mask+'$', '')
    return path.match(r)
}

function maskNewName(path, mask) {
    if (path == '') return
    var x = 0, R = ''
    for (var m = 0; m < mask.length; m++) {
        var ch = mask[m], q = path[x], z = mask[m + 1]
        if (ch != '.' && ch != '*' && ch != '?') {
            if (q && q != '.') x++
            R += ch
        } else if (ch == '?') {
            if (q && q != '.') R += q, x++
        } else if (ch == '*' && m == mask.length - 1) {
            while (x < path.length) R += path[x++]
        } else if (ch == '*') {
            if (z == '.') {
                for (var i = path.length - 1; i >= 0; i--) if (path[i] == '.') break
                if (i < 0) {
                    R += path.substr(x, path.length) + '.'
                    i = path.length
                } else R += path.substr(x, i - x + 1)
                x = i + 1, m++
            } else if (z == '?') {
                R += path.substr(x, path.length), m++, x = path.length
            } else {
                for (var i = path.length - 1; i >= 0; i--) if (path[i] == z) break
                if (i < 0) R += path.substr(x, path.length) + z, x = path.length, m++
                else R += path.substr(x, i - x), x = i + 1
            }
        } else if (ch == '.') {
            while (x < path.length) if (path[x++] == '.') break
            R += '.'
        }
    }
    while (R[R.length - 1] == '.') R = R.substr(0, R.length - 1)
}
1
1
1
2016-10-13 01:27:15 +0000

Consegui escrever este código em BASIC para mascarar nomes de ficheiros wildcard:

REM inputs a filename and matches wildcards returning masked output filename.
FUNCTION maskNewName$ (path$, mask$)
IF path$ = "" THEN EXIT FUNCTION
IF INSTR(path$, "?") OR INSTR(path$, "*") THEN EXIT FUNCTION
x = 0
R$ = ""
FOR m = 0 TO LEN(mask$) - 1
    ch$ = MID$(mask$, m + 1, 1)
    q$ = MID$(path$, x + 1, 1)
    z$ = MID$(mask$, m + 2, 1)
    IF ch$ <> "." AND ch$ <> "*" AND ch$ <> "?" THEN
        IF LEN(q$) AND q$ <> "." THEN x = x + 1
        R$ = R$ + ch$
    ELSE
        IF ch$ = "?" THEN
            IF LEN(q$) AND q$ <> "." THEN R$ = R$ + q$: x = x + 1
        ELSE
            IF ch$ = "*" AND m = LEN(mask$) - 1 THEN
                WHILE x < LEN(path$)
                    R$ = R$ + MID$(path$, x + 1, 1)
                    x = x + 1
                WEND
            ELSE
                IF ch$ = "*" THEN
                    IF z$ = "." THEN
                        FOR i = LEN(path$) - 1 TO 0 STEP -1
                            IF MID$(path$, i + 1, 1) = "." THEN EXIT FOR
                        NEXT
                        IF i < 0 THEN
                            R$ = R$ + MID$(path$, x + 1) + "."
                            i = LEN(path$)
                        ELSE
                            R$ = R$ + MID$(path$, x + 1, i - x + 1)
                        END IF
                        x = i + 1
                        m = m + 1
                    ELSE
                        IF z$ = "?" THEN
                            R$ = R$ + MID$(path$, x + 1, LEN(path$))
                            m = m + 1
                            x = LEN(path$)
                        ELSE
                            FOR i = LEN(path$) - 1 TO 0 STEP -1
                                'IF MID$(path$, i + 1, 1) = z$ THEN EXIT FOR
                                IF UCASE$(MID$(path$, i + 1, 1)) = UCASE$(z$) THEN EXIT FOR
                            NEXT
                            IF i < 0 THEN
                                R$ = R$ + MID$(path$, x + 1, LEN(path$)) + z$
                                x = LEN(path$)
                                m = m + 1
                            ELSE
                                R$ = R$ + MID$(path$, x + 1, i - x)
                                x = i + 1
                            END IF
                        END IF
                    END IF
                ELSE
                    IF ch$ = "." THEN
                        DO WHILE x < LEN(path$)
                            IF MID$(path$, x + 1, 1) = "." THEN
                                x = x + 1
                                EXIT DO
                            END IF
                            x = x + 1
                        LOOP
                        R$ = R$ + "."
                    END IF
                END IF
            END IF
        END IF
    END IF
NEXT
DO WHILE RIGHT$(R$, 1) = "."
    R$ = LEFT$(R$, LEN(R$) - 1)
LOOP
R$ = RTRIM$(R$)
maskNewName$ = R$
END FUNCTION