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

Come interpreta i caratteri jolly il comando RENAME di Windows?

**Come interpreta i caratteri jolly il comando RENAME (REN) di Windows?

La funzione HELP integrata non è di alcun aiuto - non si occupa affatto dei caratteri jolly.

Il Microsoft technet XP online help non è molto meglio. Ecco tutto quello che ha da dire riguardo ai caratteri jolly:

“Puoi usare i caratteri jolly (* e ?) in entrambi i parametri del nome del file. Se usi i caratteri jolly in filename2, i caratteri rappresentati dai caratteri jolly saranno identici ai caratteri corrispondenti in filename1.”

Non è di grande aiuto - ci sono molti modi in cui questa dichiarazione può essere interpretata.

Sono riuscito a usare con successo i caratteri jolly nel parametro filename2 in alcune occasioni, ma è sempre stato per tentativi ed errori. Non sono stato in grado di anticipare cosa funziona e cosa no. Spesso ho dovuto ricorrere a scrivere un piccolo script batch con un ciclo FOR che analizza ogni nome in modo da poter costruire ogni nuovo nome come necessario. Non molto conveniente.

Se conoscessi le regole di come vengono processati i caratteri jolly, immagino che potrei usare il comando RENAME in modo più efficace senza dover ricorrere al batch così spesso. Naturalmente conoscere le regole gioverebbe anche allo sviluppo del batch.

(Sì - questo è un caso in cui sto postando una domanda e risposta abbinata. Mi sono stancato di non conoscere le regole e ho deciso di sperimentare per conto mio. Immagino che molti altri possano essere interessati a ciò che ho scoperto)

Risposte (4)

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

Queste regole sono state scoperte dopo lunghi test su una macchina Vista. Nessun test è stato fatto con unicode nei nomi dei file._

RENAME richiede 2 parametri - una sourceMask, seguita da una targetMask. Sia la sourceMask che la targetMask possono contenere caratteri jolly * e/o ?. Il comportamento dei caratteri jolly cambia leggermente tra le maschere di origine e di destinazione.

Note - REN può essere usato per rinominare una cartella, ma i caratteri jolly non sono non ammessi né nella sourceMask né nella targetMask quando si rinomina una cartella. Se la sourceMask corrisponde ad almeno un file, allora il file o i file saranno rinominati e le cartelle saranno ignorate. Se la sourceMask corrisponde solo alle cartelle e non ai file, allora viene generato un errore di sintassi se i caratteri jolly appaiono in source o target. Se la sourceMask non corrisponde a nulla, allora viene generato un errore “file not found”.

Inoltre, quando si rinominano i file, i caratteri jolly sono ammessi solo nella porzione del nome del file della sourceMask. I caratteri jolly non sono ammessi nel percorso che porta al nome del file.

sourceMask

La sourceMask funziona come un filtro per determinare quali file vengono rinominati. I caratteri jolly funzionano qui come con qualsiasi altro comando che filtra i nomi dei file.

  • ? - Corrisponde a qualsiasi carattere 0 o 1 eccetto . Questo carattere jolly è avido - consuma sempre il carattere successivo se non è uno . Tuttavia non corrisponderà a nulla senza fallire se alla fine del nome o se il carattere successivo è uno .

  • * - Corrisponde a qualsiasi carattere 0 o più incluso . (con un'eccezione sotto). Questo carattere jolly non è avido. Corrisponderà tanto o poco quanto è necessario per permettere ai caratteri successivi di corrispondere.

Tutti i caratteri non jolly devono corrispondere a se stessi, con alcune eccezioni di casi speciali.

  • . - Corrisponde a se stesso o può corrispondere alla fine del nome (niente) se non rimangono altri caratteri. (Nota - un nome Windows valido non può finire con .)

  • {space} - Corrisponde a se stesso o può corrispondere alla fine del nome (niente) se non rimangono altri caratteri. (Nota - un nome Windows valido non può finire con {space})

  • *. alla fine - Corrisponde a qualsiasi 0 o più caratteri eccetto . Lo . terminante può essere in realtà qualsiasi combinazione di . e {space} finché l'ultimo carattere della maschera è . Questa è la sola e unica eccezione dove * non corrisponde semplicemente a qualsiasi serie di caratteri.

Le regole di cui sopra non sono così complesse. Ma c'è un'altra regola molto importante che rende la situazione confusa: La sourceMask viene confrontata sia con il nome lungo che con il nome corto 8.3 (se esiste). Quest'ultima regola può rendere l'interpretazione dei risultati molto complicata, perché non è sempre ovvio quando la maschera viene confrontata con il nome corto.

È possibile usare RegEdit per disabilitare la generazione di nomi brevi 8.3 sui volumi NTFS, a questo punto l'interpretazione dei risultati della maschera di file è molto più semplice. Qualsiasi nome breve generato prima di disabilitare i nomi brevi rimarrà.

targetMask

Nota - Non ho fatto alcun test rigoroso, ma sembra che queste stesse regole funzionino anche per il nome di destinazione del comando COPY

La targetMask specifica il nuovo nome. Viene sempre applicata al nome lungo completo; la targetMask non viene mai applicata al nome corto 8.3, anche se la sourceMask corrispondeva al nome corto 8.3.

La presenza o l'assenza di caratteri jolly nella sourceMask non ha alcun impatto su come i caratteri jolly vengono elaborati nella targetMask.

Nella seguente discussione - c rappresenta qualsiasi carattere che non sia *, ?, o .

La targetMask è processata rispetto al nome sorgente rigorosamente da sinistra a destra senza back-tracking.

  • c - Fa avanzare la posizione all'interno del nome sorgente solo se il carattere sorgente non è ., e aggiunge sempre c al nome target. (Sostituisce il carattere che era nel sorgente con c, ma non sostituisce mai .)

  • ? - Corrisponde al prossimo carattere del nome lungo sorgente e lo aggiunge al nome di destinazione finché il carattere sorgente non è . Se il prossimo carattere è . o se è alla fine del nome sorgente allora nessun carattere viene aggiunto al risultato e la posizione corrente all'interno del nome sorgente è invariata.

  • * alla fine di targetMask - Aggiunge tutti i caratteri rimanenti dal sorgente al target. Se è già alla fine del sorgente, allora non fa nulla.

  • *c - Corrisponde a tutti i caratteri dell'origine dalla posizione corrente fino all'ultima occorrenza di c (corrispondenza sensibile alle maiuscole e alle minuscole) e aggiunge l'insieme dei caratteri corrispondenti al nome di destinazione. Se c non viene trovato, allora tutti i caratteri rimanenti dal sorgente vengono aggiunti, seguiti da c Questa è l'unica situazione di cui sono a conoscenza in cui la corrispondenza del pattern di file di Windows è sensibile alle maiuscole.

  • *. - Corrisponde a tutti i caratteri del sorgente dalla posizione corrente fino all’ultima occorrenza di . (greedy match) e aggiunge l'insieme di al nome di destinazione. Se . non viene trovato, allora vengono aggiunti tutti i caratteri rimanenti dal sorgente, seguiti da .

  • *? - Aggiunge tutti i caratteri rimanenti dal sorgente alla destinazione. Se è già alla fine del sorgente allora non fa nulla.

  • . senza * davanti - Avanza la posizione nel sorgente fino alla prima occorrenza di . senza copiare alcun carattere, e aggiunge . al nome di destinazione. Se . non si trova nel sorgente, allora avanza fino alla fine del sorgente e aggiunge . al nome di destinazione.

Dopo che la targetMask è stata esaurita, qualsiasi traccia di . e {space} viene tagliata dalla fine del nome di destinazione risultante, perché i nomi di file Windows non possono finire con . o {space}

Alcuni esempi pratici

Sostituisci un carattere nella 1a e 3a posizione prima di qualsiasi estensione (aggiunge un 2° o 3° carattere se non esiste ancora)

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

Cambiare l'estensione (finale) di ogni file

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

Aggiungere un'estensione ad ogni file

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

Rimuovere qualsiasi estensione extra dopo l'estensione iniziale. Nota che ? adeguato deve essere usato per preservare il nome completo esistente e l'estensione iniziale.

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)

Come sopra, ma filtra i file con nome iniziale e/o estensione più lunghi di 5 caratteri in modo che non vengano troncati. (Ovviamente si potrebbe aggiungere un ulteriore ? alle due estremità di targetMask per preservare nomi ed estensioni fino a 6 caratteri)

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)

Cambia i caratteri dopo l'ultimo _ nel nome e cerca di preservare l'estensione. (Non funziona correttamente se _ appare nell'estensione)

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)

Qualsiasi nome può essere suddiviso in componenti che sono delimitati da . I caratteri possono essere solo aggiunti o cancellati dalla fine di ogni componente. I caratteri non possono essere cancellati o aggiunti all'inizio o al centro di un componente conservando il resto con i caratteri jolly. Le sostituzioni sono permesse ovunque.

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 i nomi brevi sono abilitati, allora una sourceMask con almeno 8 ? per il nome e almeno 3 ? per l'estensione corrisponderà a tutti i file perché corrisponderà sempre al nome breve 8.3.

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

Utile stranezza/bug? per cancellare i prefissi dei nomi

Questo post del SuperUser descrive come una serie di slash (/) può essere usata per cancellare i caratteri iniziali dal nome di un file. Una barra è necessaria per ogni carattere da cancellare. Ho confermato il comportamento su una macchina Windows 10.

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

Questa tecnica funziona solo se entrambe le maschere di origine e di destinazione sono racchiuse tra doppi apici. Tutti i seguenti moduli senza le virgolette necessarie falliscono con questo errore: 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

Lo / non può essere usato per rimuovere alcun carattere nel mezzo o alla fine di un nome di file. Può solo rimuovere i caratteri iniziali (prefisso). Notate anche che questa tecnica non funziona con i nomi delle cartelle.

Tecnicamente lo / non funziona come un carattere jolly. Piuttosto sta facendo una semplice sostituzione di caratteri, ma poi dopo la sostituzione, il comando REN riconosce che / non è valido in un nome di file, e rimuove le barre iniziali / dal nome. REN dà un errore di sintassi se rileva / nel mezzo di un nome di destinazione.

Possibile bug di RENAME - un singolo comando può rinominare lo stesso file due volte!

Inizio in una cartella di prova vuota:

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

Credo che la sourceMask *1* corrisponda prima al nome lungo del file, e il file viene rinominato al risultato atteso di 223456789.123.x. RENAME poi continua a cercare altri file da processare e trova il file appena nominato tramite il nuovo nome corto di 223456~1.X. Il file viene poi rinominato di nuovo dando il risultato finale di 223456789.123.xx.

Se disabilito la generazione del nome 8.3 allora il RENAME dà il risultato atteso.

Non ho elaborato completamente tutte le condizioni di attivazione che devono esistere per indurre questo strano comportamento. Ero preoccupato che potesse essere possibile creare un RENAME ricorsivo senza fine, ma non sono mai stato in grado di indurne uno.

Credo che tutte le seguenti condizioni debbano essere vere per indurre il bug. Ogni caso di bug che ho visto aveva le seguenti condizioni, ma non tutti i casi che soddisfacevano le seguenti condizioni erano bug.

  • I nomi brevi 8.3 devono essere abilitati
  • La sourceMask deve corrispondere al nome lungo originale.
  • La rinomina iniziale deve generare un nome corto che corrisponda anche alla sourceMask
  • Il nome corto iniziale rinominato deve essere successivo al nome corto originale (se esisteva?)
4
4
4
2014-12-16 10:13:11 +0000

Simile a exebook, ecco un'implementazione C# per ottenere il nome del file di destinazione da un file sorgente.

Ho trovato 1 piccolo errore negli esempi di dbenham:

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

Ecco il codice:

/// <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('.', ' ');
    }

Ed ecco un metodo di test NUnit per testare gli esempi:

[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

Forse qualcuno può trovarlo utile. Questo codice JavaScript è basato sulla risposta di dbenham sopra.

Non ho testato molto sourceMask, ma targetMask corrisponde a tutti gli esempi dati da 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

Sono riuscito a scrivere questo codice in BASIC per mascherare i nomi di file jolly:

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