Como o comando RENAME do Windows interpreta caracteres curinga?

78

Como o comando RENAME (REN) do Windows interpreta caracteres curinga?

O recurso de ajuda incorporado não ajuda em nada - ele não trata de curingas.

A ajuda online do Microsoft technet XP não é muito melhor. Aqui está tudo o que tem a dizer sobre caracteres curinga:

"Você pode usar caracteres curinga ( *e ?) em qualquer parâmetro de nome de arquivo. Se você usar caracteres curinga no nome do arquivo2, os caracteres representados pelos caracteres curinga serão idênticos aos caracteres correspondentes no nome do arquivo1."

Não há muita ajuda - há muitas maneiras pelas quais essa declaração pode ser interpretada.

Consegui usar com sucesso caracteres curinga no parâmetro filename2 em algumas ocasiões, mas sempre foi tentativa e erro. Não pude prever o que funciona e o que não funciona. Freqüentemente, tive que recorrer a escrever um pequeno script em lote com um loop FOR que analisa cada nome para que eu possa criar cada novo nome conforme necessário. Não é muito conveniente.

Se eu soubesse as regras de como os curingas são processados, acho que poderia usar o comando RENAME com mais eficiência sem precisar 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 postando uma pergunta e resposta emparelhadas. Me cansei de não conhecer as regras e decidi experimentar por conta própria. Acho que muitos outros podem estar interessados ​​no que descobri)

dbenham
fonte
Há montes de bons exemplos de como mudar o nome com curingas aqui: lagmonster.org/docs/DOS7/z-ren1.html
Matthew Bloqueio
5
@ MatthewLock - Link interessante, mas essas regras e exemplos são para MSDOS 7, não para Windows. Existem diferenças significativas. Por exemplo, o MSDOS não permite anexar caracteres adicionais depois *, o Windows permite. Isso tem enormes consequências. Eu gostaria de saber sobre esse site; isso poderia ter facilitado minha investigação. As regras do MSDOS7 são significativamente diferentes das regras antigas do DOS, antes de nomes de arquivos longos, e são um passo na direção de como o Windows lida com isso. Eu havia encontrado as regras do DOS de nome de arquivo longo e não valiam a pena investigar.
dbenham
Eu não sabia disso;)
Matthew Lock

Respostas:

117

Essas regras foram descobertas após extensos testes em uma máquina Vista. Não foram realizados testes com unicode nos nomes dos arquivos.

RENAME requer 2 parâmetros - um sourceMask, seguido por um targetMask. O sourceMask e o targetMask podem conter *e / ou ?curingas. O comportamento dos curingas muda ligeiramente entre as máscaras de origem e de destino.

Nota - O REN pode ser usado para renomear uma pasta, mas curingas não são permitidos no sourceMask ou no targetMask ao renomear uma pasta. Se o sourceMask corresponder a pelo menos um arquivo, os arquivos serão renomeados e as pastas serão ignoradas. Se o sourceMask corresponder apenas a pastas e não a arquivos, será gerado um erro de sintaxe se caracteres curinga aparecerem na origem ou no destino. Se o sourceMask não corresponder a nada, ocorrerá um erro "arquivo não encontrado".

Além disso, ao renomear arquivos, os curingas são permitidos apenas na parte do nome do arquivo da sourceMask. Curingas não são permitidos no caminho que leva ao nome do arquivo.

sourceMask

O sourceMask funciona como um filtro para determinar quais arquivos são renomeados. Os curingas funcionam aqui da mesma forma que com qualquer outro comando que filtra nomes de arquivos.

  • ?- Corresponde a qualquer caractere 0 ou 1, exceto . Este curinga é ganancioso - ele sempre consome o próximo caractere se não for um. . No entanto, ele não corresponderá a nada sem falhas se no final do nome ou se o próximo caractere for um.

  • *- Corresponde a 0 ou mais caracteres, incluindo . (com uma exceção abaixo). Este curinga não é ganancioso. Combina o mínimo ou o necessário para permitir a correspondência dos caracteres subseqüentes.

Todos os caracteres não curinga devem corresponder a si mesmos, com algumas exceções de caso especiais.

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

  • {space}- Corresponde a si próprio ou pode corresponder ao final do nome (nada) se não houver mais caracteres. (Observação - um nome válido do Windows não pode terminar com {space})

  • *.no final - Corresponde a 0 ou mais caracteres, exceto . A terminação .pode realmente ser qualquer combinação .e {space}desde que o último caractere da máscara seja . Esta é a única exceção em *que simplesmente não corresponde a nenhum conjunto de caracteres.

As regras acima não são tão complexas. Mas há mais uma regra muito importante que torna a situação confusa: O sourceMask é comparado com o nome longo e o nome 8.3 curto (se existir). Essa última regra pode tornar a interpretação dos resultados muito complicada, porque nem sempre é óbvio quando a máscara está sendo correspondida pelo nome abreviado.

É possível usar o RegEdit para desativar a geração de nomes abreviados 8.3 em volumes NTFS, momento em que a interpretação dos resultados da máscara de arquivo é muito mais direta. Quaisquer nomes abreviados que foram gerados antes da desativação de nomes abreviados permanecerão.

targetMask

Nota - Eu não fiz nenhum teste rigoroso, mas parece que essas mesmas regras também funcionam para o nome do destino do comando COPY

O targetMask especifica o novo nome. É sempre aplicado ao nome completo completo; O targetMask nunca é aplicado ao nome abreviado 8.3, mesmo se o sourceMask corresponder ao nome abreviado 8.3.

A presença ou ausência de curingas no sourceMask não tem impacto sobre como os curingas são processados ​​no targetMask.

Na discussão a seguir - crepresenta qualquer caractere que não é *, ?ou.

O targetMask é processado com relação ao nome da fonte estritamente da esquerda para a direita, sem rastreamento de retorno.

  • c- Avança a posição dentro do nome da fonte, desde que o próximo caractere não seja .e anexa cao nome do destino. (Substitui o caractere que estava na fonte por c, mas nunca substitui .)

  • ?- Corresponde ao próximo caractere do nome longo da fonte e o anexa ao nome do destino, desde que o próximo caractere não seja. . Se o próximo caractere estiver .ou se estiver no final do nome da fonte, nenhum caractere será adicionado ao resultado e ao atual. A posição no nome da fonte permanece inalterada.

  • *no final de targetMask - anexa todos os caracteres restantes da origem ao destino. Se já estiver no final da fonte, não fará nada.

  • *c- Corresponde a todos os caracteres de origem da posição atual até a última ocorrência de c(correspondência gananciosa com distinção entre maiúsculas e minúsculas) e anexa o conjunto de caracteres correspondente ao nome do destino. Se cnão for encontrado, todos os caracteres restantes da origem serão anexados, seguidos por c Esta é a única situação em que sei onde a correspondência de padrões de arquivo do Windows faz distinção entre maiúsculas e minúsculas.

  • *.- Corresponde a todos os caracteres de origem da posição atual até a última ocorrência de .(correspondência gananciosa) e anexa o conjunto de caracteres correspondente ao nome do destino. Se .não for encontrado, todos os caracteres restantes da fonte serão anexados, seguidos por.

  • *?- Anexa todos os caracteres restantes da origem ao destino. Se já estiver no final da fonte, não fará nada.

  • .sem *na frente - Avança a posição na origem pela primeira vez em .sem copiar nenhum caractere e anexa .ao nome do destino. Se .não for encontrado na fonte, avança para o final da fonte e anexa .ao nome do destino.

Após a targetMask foi esgotado, qualquer fuga .e {space}são aparadas off no final do nome do alvo resultante porque nomes de arquivos do Windows não pode terminar com .ou{space}

Alguns exemplos práticos

Substitua um caractere na 1ª e na 3ª posições antes de qualquer extensão (adiciona um 2º ou 3º caractere se ele 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 arquivo

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

Anexar uma extensão a cada arquivo

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

Remova qualquer extensão extra após a extensão inicial. Observe 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 filtre os arquivos com nome e / ou extensão inicial com mais de 5 caracteres, para que não sejam truncados. (Obviamente, pode adicionar um adicional ?em cada extremidade do targetMask para preservar nomes e extensões com 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)

Altere os caracteres após o último _no nome e tente preservar a extensão. (Não funciona corretamente 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 . caracteres, que podem ser anexados ou excluídos apenas no final de cada componente. Os caracteres não podem ser excluídos ou adicionados ao início ou no meio de um componente, preservando o restante com caracteres curinga. 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 abreviados estiverem ativados, uma sourceMask com pelo menos 8 ?para o nome e pelo menos 3 ?para a extensão corresponderá a todos os arquivos, pois sempre corresponderá ao nome 8.3 abreviado.

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


Peculiar / bug útil? para excluir prefixos de nome

Esta postagem do Superusuário descreve como um conjunto de barras ( /) pode ser usado para excluir caracteres iniciais de um nome de arquivo. Uma barra é necessária para cada caractere a ser excluído. Confirmei o comportamento em uma máquina Windows 10.

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

Essa técnica funciona apenas se as máscaras de origem e de destino estiverem entre aspas duplas. Todos os seguintes formulários sem as cotações 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

Não /pode ser usado para remover nenhum caractere no meio ou no final de um nome de arquivo. Só pode remover caracteres iniciais (prefixo).

Tecnicamente, o /não está funcionando como um curinga. Em vez disso, ele está fazendo uma substituição simples de caracteres, mas, após a substituição, o comando REN reconhece que /não é válido em um nome de arquivo e retira as /barras principais do nome. O REN gera um erro de sintaxe se detectar /no meio do nome de um destino.


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

Iniciando em uma 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 o sourceMask *1*corresponda primeiro ao nome do arquivo longo, e o arquivo é renomeado para o resultado esperado de 223456789.123.x. RENAME continua a procurar mais arquivos para processar e localiza o novo arquivo nomeado por meio do novo nome abreviado de 223456~1.X. O arquivo é renomeado novamente, fornecendo o resultado final de 223456789.123.xx.

Se eu desativar a geração de nomes 8.3, o RENAME fornecerá o resultado esperado.

Ainda não elaborei completamente todas as condições de gatilho que devem existir para induzir esse comportamento estranho. Eu estava preocupado que fosse possível criar um RENAME recursivo sem fim, mas nunca consegui induzi-lo.

Acredito que todos os itens a seguir devem ser verdadeiros para induzir o bug. Cada caso com bug que eu vi tinha as seguintes condições, mas nem todos os casos que atendiam às seguintes condições eram com bug.

  • Nomes curtos 8.3 devem estar ativados
  • O sourceMask deve corresponder ao nome longo original.
  • A renomeação inicial deve gerar um nome abreviado que também corresponda ao sourceMask
  • O nome abreviado renomeado inicial deve classificar depois do nome abreviado original (se existir?)
dbenham
fonte
6
Que resposta completa .. +1.
Meder omuraliev
Tremendamente elaborado!
precisa
13
Com base nisso, a Microsoft deve apenas adicionar "Para uso, consulte superuser.com/a/475875 " em REN /?.
Efotinis
4
@CAD - Esta resposta é 100% conteúdo original que Simon incluiu em seu site mediante solicitação. Olhe na parte inferior da página do SS64 e você verá que Simon me dá crédito pelo trabalho.
dbenham
2
@ JacksOnF1re - Novas informações / técnicas adicionadas à minha resposta. Você pode realmente eliminar o seu Copy of prefixo usando uma técnica barra obscuro:ren "Copy of *.txt" "////////*"
dbenham
4

Semelhante ao exebook, aqui está uma implementação em C # para obter o nome do arquivo de destino de um arquivo de origem.

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

    }
assustador
fonte
Obrigado pela atenção do erro no meu exemplo. Eu editei minha resposta para corrigi-lo.
dbenham
1

Eu consegui escrever esse código no BASIC para mascarar nomes de arquivos curinga:

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
eoredson
fonte
4
Você pode esclarecer como isso responde ao que foi solicitado na pergunta?
fixer1234
Ele replica a função que o REN usa para a correspondência de curingas, como o processamento de REN * .TMP * .DOC, dependendo de como a função é chamada antes de renomear os nomes de arquivos.
eoredson
1

Talvez alguém possa achar isso útil. Este código JavaScript é baseado na resposta de dbenham acima.

Não testei sourceMaskmuito, mas targetMaskcorresponde a todos os exemplos dados por 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)
}
exebook
fonte