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

¿Cómo interpreta el comando RENAME de Windows los comodines?

**¿Cómo interpreta el comando RENAME (REN) de Windows los comodines?

La ayuda incorporada no es de ninguna ayuda - no trata los comodines en absoluto.

La ayuda en línea de Microsoft technet XP no es mucho mejor. Esto es todo lo que tiene que decir con respecto a los comodines:

“Puede utilizar comodines (* y ?) en cualquiera de los parámetros del nombre del archivo. Si utiliza comodines en filename2, los caracteres representados por los comodines serán idénticos a los caracteres correspondientes en filename1.”

No es de mucha ayuda - hay muchas maneras de interpretar esa declaración.

He conseguido utilizar con éxito los comodines en el parámetro nombredearchivo2 en algunas ocasiones, pero siempre ha sido por ensayo y error. No he sido capaz de anticipar lo que funciona y lo que no. Con frecuencia he tenido que recurrir a escribir un pequeño script por lotes con un bucle FOR que analiza cada nombre para poder construir cada nuevo nombre según sea necesario. No es muy conveniente.

Si conociera las reglas de cómo se procesan los comodines, me imagino que podría utilizar el comando RENAME de forma más efectiva sin tener que recurrir a los lotes tan a menudo. Por supuesto, conocer las reglas también beneficiaría el desarrollo por lotes.

(Sí - este es un caso en el que estoy publicando una pregunta y una respuesta emparejadas. Me he cansado de no conocer las reglas y he decidido experimentar por mi cuenta. Me imagino que a muchos otros les puede interesar lo que he descubierto)

Respuestas (4)

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

Estas reglas fueron descubiertas después de extensas pruebas en una máquina Vista. No se hicieron pruebas con unicode en los nombres de archivo._

RENAME requiere 2 parámetros - un sourceMask, seguido de un targetMask. Tanto la sourceMask como la targetMask pueden contener comodines * y/o ?. El comportamiento de los comodines cambia ligeramente entre las máscaras de origen y de destino.

Nota - REN puede usarse para renombrar una carpeta, pero los comodines no están permitidos ni en la sourceMask ni en la targetMask al renombrar una carpeta. Si la sourceMask coincide con al menos un archivo, se renombrarán los archivos y se ignorarán las carpetas. Si la sourceMask sólo coincide con carpetas y no con archivos, se generará un error de sintaxis si aparecen comodines en el origen o en el destino. Si el sourceMask no coincide con nada, entonces se genera un error de “archivo no encontrado”.

Además, al renombrar archivos, los comodines sólo se permiten en la parte del nombre del archivo del sourceMask. Los comodines no están permitidos en la ruta que lleva al nombre del archivo.

sourceMask

El sourceMask funciona como un filtro para determinar qué archivos se renombran. Los comodines funcionan aquí igual que con cualquier otro comando que filtre nombres de archivos.

  • ? - Coincide con cualquier carácter 0 o 1 excepto . Este comodín es codicioso - siempre consume el siguiente carácter si no es un . Sin embargo no coincidirá con nada sin fallo si al final del nombre o si el siguiente carácter es un .

  • * - Coincide con cualquier carácter 0 o más incluyendo . (con una excepción más abajo). Este comodín no es codicioso. Coincidirá con lo poco o mucho que sea necesario para permitir que los caracteres subsiguientes coincidan.

Todos los caracteres que no son comodines deben coincidir por sí mismos, con algunas excepciones de casos especiales.

  • . - Coincide consigo mismo o puede coincidir con el final del nombre (nada) si no quedan más caracteres. (Nota - un nombre válido de Windows no puede terminar con .)

  • {space} - Coincide consigo mismo o puede coincidir con el final del nombre (nada) si no quedan más caracteres. (Nota - un nombre válido de Windows no puede terminar con {space})

  • *. al final - Coincide con cualquier 0 o más caracteres excepto . El . final puede ser en realidad cualquier combinación de . y {space} siempre que el último carácter de la máscara sea . Esta es la única excepción en la que * no coincide simplemente con cualquier conjunto de caracteres.

Las reglas anteriores no son tan complejas. Pero hay otra regla muy importante que hace que la situación sea confusa: El sourceMask se compara tanto con el nombre largo como con el nombre corto 8.3 (si existe). Esta última regla puede hacer que la interpretación de los resultados sea muy complicada, ya que no siempre es obvio cuando la máscara coincide con el nombre corto.

Es posible utilizar RegEdit para desactivar la generación de nombres cortos 8.3 en los volúmenes NTFS, en cuyo caso la interpretación de los resultados de la máscara de archivo es mucho más sencilla. Cualquier nombre corto que se haya generado antes de deshabilitar los nombres cortos se mantendrá.

targetMask

Nota - No he hecho ninguna prueba rigurosa, pero parece que estas mismas reglas también funcionan para el nombre de destino del comando COPY

El targetMask especifica el nuevo nombre. Siempre se aplica al nombre largo completo; La targetMask nunca se aplica al nombre corto 8.3, incluso si la sourceMask coincidió con el nombre corto 8.3.

La presencia o ausencia de comodines en la sourceMask no tiene ningún impacto en cómo se procesan los comodines en la targetMask.

En la siguiente discusión - c representa cualquier carácter que no sea *, ?, o .

La targetMask se procesa contra el nombre fuente estrictamente de izquierda a derecha sin retroceso.

  • c - Avanza la posición dentro del nombre de origen sólo si el carácter de origen no es ., y siempre añade c al nombre de destino. (Reemplaza el carácter que estaba en el origen con c, pero nunca reemplaza a .)

  • ? - Coincide con el siguiente carácter del nombre largo de origen y lo añade al nombre de destino siempre que el carácter de origen no sea . Si el siguiente carácter es . o si está al final del nombre de origen entonces no se añade ningún carácter al resultado y la posición actual dentro del nombre de origen no cambia.

  • * al final de targetMask - Añade todos los caracteres restantes del origen al destino. Si ya está al final de la fuente, no hace nada.

  • *c - Busca todos los caracteres del origen desde la posición actual hasta la última ocurrencia de c (coincidencia codiciosa sensible a mayúsculas y minúsculas) y añade el conjunto de caracteres coincidentes al nombre de destino. Si no se encuentra c, entonces se añaden todos los caracteres restantes del origen, seguidos de c Esta es la única situación que conozco en la que la coincidencia de patrones de archivos de Windows distingue entre mayúsculas y minúsculas.

  • *. - Coincide con todos los caracteres del origen desde la posición actual hasta la última aparición de . (coincidencia codiciosa) y añade el conjunto de al nombre de destino. Si no se encuentra ., se añaden todos los caracteres restantes del origen, seguidos de .

  • *? - Añade todos los caracteres restantes del origen al destino. Si ya está al final del origen, no hace nada.

  • . sin * delante - Avanza la posición en el origen hasta la primera aparición de . sin copiar ningún carácter, y añade . al nombre de destino. Si . no se encuentra en el origen, entonces avanza hasta el final del origen y añade . al nombre de destino.

Una vez agotada la targetMask, cualquier . y {space} al final del nombre de destino resultante porque los nombres de archivo de Windows no pueden terminar con . o {space}

Algunos ejemplos prácticos

Sustituye un carácter en la 1ª y 3ª posición antes de cualquier extensión (añade un 2º o 3º carácter si aún no existe)

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

Cambia la extensión (final) de cada archivo

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

Añade una extensión a cada archivo

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

Elimina cualquier extensión extra después de la inicial. Tenga en cuenta que el ? adecuado debe utilizarse para conservar el nombre completo existente y la extensión 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)

Igual que el anterior, pero filtrando los archivos con nombre inicial y/o extensión de más de 5 caracteres para que no se trunquen. (Obviamente, podría añadir un ? adicional en cada extremo de targetMask para preservar nombres y extensiones de hasta 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)

Cambia los caracteres después del último _ en el nombre e intenta preservar la extensión. (No funciona correctamente si el _ aparece en la extensión)

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)

Cualquier nombre puede dividirse en componentes delimitados por . Los caracteres sólo pueden añadirse o eliminarse del final de cada componente. No se pueden eliminar o añadir caracteres al principio o a la mitad de un componente conservando el resto con comodines. Las sustituciones están permitidas en cualquier 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

Si los nombres cortos están habilitados, entonces una sourceMask con al menos 8 ? para el nombre y al menos 3 ? para la extensión coincidirá con todos los archivos porque siempre coincidirá con el nombre corto 8.3.

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

¿Una peculiaridad/bug? para borrar los prefijos de los nombres

Este post de SuperUser describe cómo se puede utilizar un conjunto de barras inclinadas (/) para borrar los caracteres iniciales de un nombre de archivo. Se requiere una barra por cada carácter a eliminar. He confirmado el comportamiento en una máquina Windows 10.

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

Esta técnica sólo funciona si tanto la máscara de origen como la de destino están entre comillas dobles. Todas las formas siguientes sin las comillas requeridas fallan con este error: 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

El / no puede utilizarse para eliminar ningún carácter en el medio o al final de un nombre de archivo. Sólo puede eliminar los caracteres iniciales (prefijos). Tenga en cuenta también que esta técnica no funciona con los nombres de las carpetas.

Técnicamente el / no funciona como un comodín. Más bien está haciendo una simple sustitución de caracteres, pero después de la sustitución, el comando REN reconoce que / no es válido en un nombre de archivo, y elimina las barras iniciales / del nombre. REN da un error de sintaxis si detecta / en medio de un nombre de destino.

Posible error de RENAME - ¡un solo comando puede renombrar el mismo archivo dos veces!

Comenzando en una carpeta de prueba vacía:

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

Creo que el sourceMask *1* primero coincide con el nombre largo del archivo, y el archivo es renombrado al resultado esperado de 223456789.123.x. RENAME entonces continúa buscando más archivos para procesar y encuentra el archivo recién nombrado a través del nuevo nombre corto de 223456~1.X. El archivo es renombrado de nuevo dando el resultado final de 223456789.123.xx.

Si desactivo la generación de nombres de 8.3 entonces el RENAME da el resultado esperado.

Aún no he podido averiguar todas las condiciones de activación que deben existir para inducir este extraño comportamiento. Me preocupaba que fuera posible crear un RENAME recursivo interminable, pero nunca pude inducirlo.

Creo que todo lo siguiente debe ser cierto para inducir el error. Todos los casos con fallos que vi tenían las siguientes condiciones, pero no todos los casos que cumplían las siguientes condiciones tenían fallos.

  • Los nombres cortos 8.3 deben estar habilitados
  • La sourceMask debe coincidir con el nombre largo original.
  • El renombrado inicial debe generar un nombre corto que también coincida con el sourceMask
  • El nombre corto inicial renombrado debe ser posterior al nombre corto original (si es que existía)
4
4
4
2014-12-16 10:13:11 +0000

De forma similar a exebook, aquí hay una implementación en C# para obtener el nombre del archivo de destino a partir de un archivo de origen.

He encontrado un pequeño error en los ejemplos de dbenham:

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

Aquí está el 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('.', ' ');
    }

Y aquí hay un método de prueba NUnit para probar los ejemplos:

[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

Tal vez alguien pueda encontrar esto útil. Este código JavaScript está basado en la respuesta de dbenham de arriba.

No he probado mucho el sourceMask, pero el targetMask coincide con todos los ejemplos 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)
}
1
1
1
2016-10-13 01:27:15 +0000

He conseguido escribir este código en BASIC para enmascarar los nombres de archivo comodín:

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