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

Jak polecenie RENAME w systemie Windows interpretuje symbole wieloznaczne?

**Jak polecenie Windows RENAME (REN) interpretuje symbole wieloznaczne?

Wbudowana funkcja HELP nie jest pomocna - nie zajmuje się w ogóle symbolami wieloznacznymi.

Pomoc online Microsoft technet XP nie jest dużo lepsza. Oto wszystko, co ma do powiedzenia na temat symboli wieloznacznych:

“Możesz używać symboli wieloznacznych (* i ?) w każdym z parametrów nazwy pliku. Jeśli użyjesz symboli wieloznacznych w filename2, znaki reprezentowane przez symbole wieloznaczne będą identyczne z odpowiadającymi im znakami w filename1.”

Niewiele pomaga - istnieje wiele sposobów, w jaki można zinterpretować to stwierdzenie.

Udało mi się z powodzeniem użyć symboli wieloznacznych w parametrze filename2 przy niektórych okazjach, ale zawsze było to metodą prób i błędów. Nie byłem w stanie przewidzieć, co działa, a co nie. Często musiałem uciekać się do napisania małego skryptu wsadowego z pętlą FOR, która przetwarza każdą nazwę, abym mógł zbudować każdą nową nazwę w razie potrzeby. Nie jest to zbyt wygodne.

Gdybym znał zasady przetwarzania symboli wieloznacznych, mógłbym używać komendy RENAME bardziej efektywnie, bez konieczności uciekania się do pisania skryptu wsadowego tak często. Oczywiście znajomość reguł przyniosłaby również korzyści przy tworzeniu wsadów.

(Tak - to jest przypadek, w którym zamieszczam sparowane pytanie i odpowiedź. Zmęczyłem się nieznajomością zasad i postanowiłem poeksperymentować na własną rękę. Myślę, że wielu innych może być zainteresowanych tym, co odkryłem).

Odpowiedzi (4)

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

Te reguły zostały odkryte po długich testach na komputerze z systemem Vista. Nie przeprowadzono testów z unicode w nazwach plików.

RENAME wymaga 2 parametrów - sourceMask, a następnie targetMask. Zarówno sourceMask jak i targetMask mogą zawierać znaki wieloznaczne * i/lub ?. Zachowanie symboli wieloznacznych zmienia się nieznacznie pomiędzy maskami źródłowymi i docelowymi.

Note - REN może być użyte do zmiany nazwy folderu, ale symbole wieloznaczne nie są dozwolone w masce źródłowej ani docelowej podczas zmiany nazwy folderu. Jeśli sourceMask pasuje do co najmniej jednego pliku, wówczas nazwa pliku(ów) zostanie zmieniona, a foldery zostaną zignorowane. Jeśli sourceMask pasuje tylko do folderów, a nie do plików, wówczas generowany jest błąd składni, jeśli w źródle lub celu pojawią się symbole wieloznaczne. Jeśli sourceMask nie pasuje do niczego, to zostanie wyświetlony błąd “nie znaleziono pliku”.

Ponadto, podczas zmiany nazwy plików, symbole wieloznaczne są dozwolone tylko w części nazwy pliku w sourceMask. Znaki wieloznaczne nie są dozwolone w ścieżce prowadzącej do nazwy pliku.

sourceMask

Maska sourceMask działa jako filtr określający, które pliki są zmieniane. Znaki wieloznaczne działają tu tak samo jak w każdej innej komendzie filtrującej nazwy plików.

  • ? - Dopasowuje dowolny 0 lub 1 znak z wyjątkiem . Ten symbol wieloznaczny jest zachłanny - zawsze pochłania następny znak, jeśli nie jest to . Jednak nie dopasuje niczego bez porażki, jeśli na końcu nazwy lub jeśli następny znak jest .

  • * - Dopasowuje dowolne 0 lub więcej znaków włączając . (z jednym wyjątkiem poniżej). Ten symbol wieloznaczny nie jest zachłanny. Dopasuje tak mało lub tak dużo, jak jest to potrzebne, by umożliwić dopasowanie kolejnych znaków.

Wszystkie znaki niezawierające znaków wieloznacznych muszą pasować same do siebie, z kilkoma specjalnymi wyjątkami.

  • . - Dopasowuje się samo lub może dopasować koniec nazwy (nic), jeśli nie pozostało więcej znaków. (Uwaga - prawidłowa nazwa Windows nie może kończyć się na .)

  • {space} - Dopasowuje się lub może dopasować koniec nazwy (nic), jeśli nie pozostanie więcej znaków. (Uwaga - poprawna nazwa Windows nie może kończyć się na {space})

  • *. na końcu - Dopasowuje dowolne 0 lub więcej znaków z wyjątkiem . Kończące . może być w rzeczywistości dowolną kombinacją . i {space} tak długo, jak ostatnim znakiem w masce jest . Jest to jedyny wyjątek, gdzie * nie dopasowuje po prostu dowolnego zestawu znaków.

Powyższe reguły nie są zbyt skomplikowane. Ale jest jeszcze jedna bardzo ważna reguła, która sprawia, że sytuacja staje się zagmatwana: SourceMask jest porównywane zarówno z długą nazwą, jak i z krótką nazwą 8.3 (jeśli istnieje). Ta ostatnia reguła może bardzo utrudnić interpretację wyników, ponieważ nie zawsze jest oczywiste, kiedy maska jest dopasowywana poprzez nazwę krótką.

Możliwe jest użycie RegEdit do wyłączenia generowania krótkich nazw 8.3 na woluminach NTFS, w którym to momencie interpretacja wyników maski plików jest znacznie prostsza. Wszystkie krótkie nazwy, które zostały wygenerowane przed wyłączeniem krótkich nazw, pozostaną.

targetMask

Uwaga - nie przeprowadziłem żadnych rygorystycznych testów, ale wydaje się, że te same zasady działają również dla nazwy docelowej polecenia COPY

Maska docelowa określa nową nazwę. Jest ona zawsze stosowana do pełnej długiej nazwy; The targetMask nigdy nie jest stosowana do krótkiej nazwy 8.3, nawet jeśli sourceMask pasowało do krótkiej nazwy 8.3.

Obecność lub brak znaków wieloznacznych w masce źródłowej nie ma wpływu na sposób przetwarzania znaków wieloznacznych w masce docelowej.

W poniższej dyskusji - c reprezentuje każdy znak, który nie jest *, ?, lub .

Maska docelowa jest przetwarzana względem nazwy źródłowej ściśle od lewej do prawej, bez cofania się.

  • c - Przesuwa pozycję w nazwie źródłowej tylko jeśli znak źródłowy nie jest ., i zawsze dołącza c do nazwy docelowej. (Zastępuje znak, który był w źródle znakiem c, ale nigdy nie zastępuje .)

  • ? - Dopasowuje następny znak z długiej nazwy źródłowej i dołącza go do nazwy docelowej tak długo, jak długo znakiem źródłowym nie jest . Jeśli następny znak jest . lub na końcu nazwy źródłowej, to żaden znak nie jest dodawany do wyniku i bieżąca pozycja w nazwie źródłowej pozostaje niezmieniona.

  • * na końcu targetMask - Dodaje wszystkie pozostałe znaki ze źródła do celu. Jeśli jest już na końcu źródła, to nie robi nic.

  • *c - Dopasowuje wszystkie znaki źródła od bieżącej pozycji do ostatniego wystąpienia c (dopasowywanie z uwzględnieniem wielkości liter) i dołącza dopasowany zestaw znaków do nazwy celu. Jeśli c nie zostanie znalezione, to dołączane są wszystkie pozostałe znaki ze źródła, a następnie c Jest to jedyna znana mi sytuacja, w której dopasowywanie wzorców plików Windows uwzględnia wielkość liter.

  • *. - Dopasowuje wszystkie znaki źródłowe od bieżącej pozycji do ostatniego wystąpienia . (dopasowanie zachłanne) i dołącza dopasowany zestaw znaków do nazwy docelowej. znaków do nazwy docelowej. Jeśli . nie zostanie znalezione, to dołączane są wszystkie pozostałe znaki ze źródła, a następnie .

  • *? - Dołącza wszystkie pozostałe znaki ze źródła do nazwy docelowej. Jeśli jest już na końcu źródła, to nie robi nic.

  • . bez * z przodu - Przesuwa pozycję w źródle o pierwsze wystąpienie . bez kopiowania żadnych znaków, i dołącza . do nazwy docelowej. Jeśli . nie znajduje się w źródle, to przesuwa się na koniec źródła i dołącza . do nazwy celu.

Po wyczerpaniu targetMask, wszelkie kończące się . i {space} są obcinane z końca wynikowej nazwy docelowej, ponieważ nazwy plików Windows nie mogą kończyć się . lub {space}

Kilka praktycznych przykładów

Zastąp znak na 1. i 3. pozycji przed jakimkolwiek rozszerzeniem (dodaje drugi lub trzeci znak, jeśli jeszcze nie istnieje)

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

Zmienia (końcowe) rozszerzenie każdego pliku

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

Dodaje rozszerzenie do każdego pliku

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

Usuwa dodatkowe rozszerzenie po rozszerzeniu początkowym. Zauważ, że odpowiednie ? musi być użyte, aby zachować pełną istniejącą nazwę i początkowe rozszerzenie.

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)

To samo co powyżej, ale filtruje pliki z początkową nazwą i/lub rozszerzeniem dłuższym niż 5 znaków, aby nie zostały obcięte. (Oczywiście można dodać dodatkowe ? na obu końcach targetMask, aby zachować nazwy i rozszerzenia o długości do 6 znaków)

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)

Zmień znaki po ostatnim _ w nazwie i spróbuj zachować rozszerzenie. (Nie działa poprawnie, jeśli _ występuje w rozszerzeniu)

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)

Dowolna nazwa może być podzielona na części składowe, które są ograniczone przez . Znaki mogą być dodawane lub usuwane tylko z końca każdej części składowej. Znaki nie mogą być usuwane lub dodawane na początku lub w środku składnika przy jednoczesnym zachowaniu reszty za pomocą symboli wieloznacznych. Substytucje są dozwolone wszędzie.

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

Jeśli włączone są krótkie nazwy, to sourceMask z co najmniej 8 ? dla nazwy i co najmniej 3 ? dla rozszerzenia będzie pasować do wszystkich plików, ponieważ zawsze będzie pasować do krótkiej nazwy 8.3.

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

Przydatna sztuczka/błąd? do usuwania prefiksów nazw

Ten post SuperUser opisuje jak zestaw ukośników (/) może być użyty do usunięcia wiodących znaków z nazwy pliku. Jeden ukośnik jest wymagany dla każdego znaku, który ma zostać usunięty. Potwierdziłem to zachowanie na maszynie z systemem Windows 10.

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

Ta technika działa tylko wtedy, gdy zarówno maska źródłowa, jak i docelowa są ujęte w cudzysłów. Wszystkie poniższe formularze bez wymaganych cudzysłowów zawodzą z tym błędem: 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

The / cannot be used to remove any characters in the middle or end of a file name. Może on jedynie usunąć znaki wiodące (prefiks). Zauważ również, że ta technika nie działa z nazwami folderów.

Technicznie rzecz biorąc, / nie działa jako symbol wieloznaczny. Raczej wykonuje proste podstawianie znaków, ale po podstawieniu, polecenie REN rozpoznaje, że / nie jest poprawne w nazwie pliku i usuwa z nazwy wiodące ukośniki /. REN podaje błąd składni, jeśli wykryje / w środku nazwy docelowej.

Możliwy błąd RENAME - jedna komenda może zmienić nazwę tego samego pliku dwa razy!

Rozpoczynamy w pustym folderze testowym:

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

Wierzę, że sourceMask *1* najpierw dopasowuje długą nazwę pliku, a plik zostaje przemianowany na oczekiwany wynik 223456789.123.x. RENAME następnie kontynuuje poszukiwanie kolejnych plików do przetworzenia i znajduje nowo nazwany plik poprzez nową krótką nazwę 223456~1.X. Następnie nazwa pliku jest zmieniana ponownie, dając ostateczny wynik 223456789.123.xx.

Jeśli wyłączę generowanie nazw w 8.3, wtedy RENAME daje oczekiwany rezultat.

Nie rozpracowałem do końca wszystkich warunków wyzwalających, które muszą istnieć, aby wywołać to dziwne zachowanie. Obawiałem się, że może być możliwe stworzenie niekończącej się rekursywnej RENAME, ale nigdy nie udało mi się jej wywołać.

Uważam, że wszystkie poniższe warunki muszą być prawdziwe, aby wywołać błąd. Każdy przypadek błędu, który widziałem miał następujące warunki, ale nie wszystkie przypadki, które spełniały poniższe warunki były błędne.

  • Krótkie nazwy 8.3 muszą być włączone
  • The sourceMask musi pasować do oryginalnej długiej nazwy.
  • Początkowa zmiana nazwy musi wygenerować nazwę krótką, która również pasuje do sourceMask
  • Początkowa zmieniona nazwa krótka musi sortować później niż oryginalna nazwa krótka (jeśli istniała?)
4
4
4
2014-12-16 10:13:11 +0000

Podobny do exebooka, oto implementacja C#, aby uzyskać docelową nazwę pliku z pliku źródłowego.

Znalazłem 1 mały błąd w przykładach dbenhama:

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

Oto kod:

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

A oto metoda testowa NUnit do przetestowania przykładów:

[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

Może ktoś może uznać to za przydatne. Ten kod JavaScript jest oparty na odpowiedzi dbenham powyżej.

Nie testowałem sourceMask bardzo dużo, ale targetMask pasuje do wszystkich przykładów podanych przez 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

Udało mi się napisać następujący kod w BASIC-u do maskowania nazw plików wieloznacznych:

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