Jak polecenie Windows RENAME interpretuje symbole wieloznaczne?

77

W jaki sposób polecenie Windows RENAME (REN) interpretuje symbole wieloznaczne?

Wbudowana funkcja POMOC nie pomaga - w ogóle nie rozwiązuje symboli wieloznacznych.

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 dowolnym parametrze nazwy pliku. Jeśli użyjesz symboli wieloznacznych w nazwie pliku 2, znaki reprezentowane przez symbole wieloznaczne będą identyczne z odpowiednimi znakami w nazwie pliku 1”.

Niewiele pomocy - istnieje wiele sposobów interpretacji tego stwierdzenia.

W niektórych przypadkach udało mi się z powodzeniem użyć symboli wieloznacznych w parametrze nazwa_pliku2 , 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 analizuje każdą nazwę, aby móc w razie potrzeby zbudować każdą nową nazwę. Niezbyt wygodne.

Gdybym znał zasady przetwarzania symboli wieloznacznych, sądzę, że mógłbym użyć polecenia RENAME bardziej efektywnie, bez konieczności uciekania się do partii tak często. Oczywiście znajomość zasad byłaby również korzystna dla rozwoju partii.

(Tak - w tym przypadku wysyłam sparowane pytanie i odpowiedź. Zmęczyło mnie to, że nie znałem zasad, i postanowiłem samodzielnie eksperymentować. Myślę, że wielu innych może być zainteresowanych tym, co odkryłem)

dbenham
źródło
Tutaj jest mnóstwo dobrych przykładów zmiany nazwy za pomocą symboli wieloznacznych: lagmonster.org/docs/DOS7/z-ren1.html
Matthew Lock
5
@MatthewLock - Ciekawe łącze, ale te zasady i przykłady dotyczą MSDOS 7, a nie Windows. Istnieją znaczne różnice. Na przykład MSDOS nie zezwala na dodawanie dodatkowych znaków po *, tak jak Windows. Ma to ogromne konsekwencje. Chciałbym jednak wiedzieć o tej stronie; mogło to ułatwić moje dochodzenie. Reguły MSDOS7 różnią się znacznie od starych reguł DOS przed długimi nazwami plików i są krokiem w kierunku, w jaki system Windows je obsługuje. Znalazłem reguły DOS dotyczące długich nazw plików i były one bezwartościowe dla mojego dochodzenia.
dbenham,
Nie wiedziałem tego;)
Matthew Lock

Odpowiedzi:

116

Reguły te zostały odkryte po szeroko zakrojonych testach na komputerze z systemem Vista. Nie przeprowadzono testów z użyciem Unicode w nazwach plików.

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

Uwaga - REN może być użyte do zmiany nazwy folderu, ale symbole wieloznaczne nie są dozwolone ani w sourceMask ani targetMask podczas zmiany nazwy folderu. Jeśli sourceMask pasuje do co najmniej jednego pliku, wówczas nazwa pliku zostanie zmieniona, a foldery zostaną zignorowane. Jeśli sourceMask pasuje tylko do folderów, a nie do plików, generowany jest błąd składniowy, jeśli symbole wieloznaczne pojawiają się w źródle lub celu. Jeśli sourceMask nic nie pasuje, wówczas pojawia się błąd „nie znaleziono pliku”.

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

sourceMask

SourceMask działa jako filtr do określania, które nazwy plików mają zostać zmienione. Symbole wieloznaczne działają tutaj tak samo, jak w przypadku każdego innego polecenia filtrującego nazwy plików.

  • ?- Odpowiada dowolnemu znakowi 0 lub 1 oprócz . tej wieloznacznej jest zachłanny - zawsze zużywa następny znak, jeśli nie jest nim. . Jednak nie pasuje do niczego bez niepowodzenia, jeśli na końcu nazwy lub jeśli następny znak jest.

  • *- Dopasowuje dowolne 0 lub więcej znaków, w tym . (z jednym wyjątkiem poniżej). Ten symbol wieloznaczny nie jest zachłanny. Będzie pasować tak mało lub tyle, ile jest potrzebne, aby umożliwić dopasowanie kolejnych znaków.

Wszystkie znaki niebędące symbolami wieloznacznymi muszą pasować do siebie, z kilkoma wyjątkami wyjątków.

  • .- Pasuje do siebie lub może pasować do końca nazwy (nic), jeśli nie ma już więcej znaków. (Uwaga - poprawna nazwa systemu Windows nie może kończyć się na .)

  • {space}- Pasuje do siebie lub może pasować do końca nazwy (nic), jeśli nie ma już więcej znaków. (Uwaga - poprawna nazwa systemu Windows nie może kończyć się na {space})

  • *.na końcu - Dopasowuje dowolne 0 lub więcej znaków z wyjątkiem . Terminacji .może być dowolną kombinacją .i {space}tak długo, jak ostatni znak w masce to . Jest to jedyny wyjątek, w którym *po prostu nie pasuje do żadnego zestawu znaków.

Powyższe zasady nie są tak skomplikowane. Ale jest jeszcze jedna bardzo ważna zasada, która sprawia, że ​​sytuacja jest myląca: maska ​​SourceMask jest porównywana zarówno z długą, jak i krótką nazwą 8.3 (jeśli istnieje). Ta ostatnia reguła może sprawić, że interpretacja wyników będzie bardzo trudna, ponieważ nie zawsze jest oczywiste, kiedy maska ​​dopasowuje się za pomocą krótkiej nazwy.

Można użyć RegEdit, aby wyłączyć generowanie krótkich nazw 8.3 na woluminach NTFS, w którym to momencie interpretacja wyników maski pliku jest znacznie prostsza. Wszelkie 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 reguły działają również w przypadku nazwy docelowej polecenia COPY

TargMask określa nową nazwę. Jest zawsze stosowane do pełnej długiej nazwy; TargMask nigdy nie jest stosowany do krótkiej nazwy 8.3, nawet jeśli sourceMask pasuje do krótkiej nazwy 8.3.

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

W poniższej dyskusji - coznacza dowolny znak, który nie jest *, ?lub.

Maska target jest przetwarzana względem nazwy źródła ściśle od lewej do prawej bez śledzenia wstecznego.

  • c- Przesuwa pozycję w obrębie nazwy źródłowej, o ile następny znak nie jest, .i dołącza cdo nazwy docelowej. (Zastępuje znak, który był w źródle c, ale nigdy nie zastępuje .)

  • ?- Dopasowuje następny znak z długiej nazwy źródłowej i dołącza go do nazwy docelowej, dopóki następny znak nie jest. . Jeśli następny znak jest .lub jeśli na końcu nazwy źródłowej, żaden wynik nie jest dodawany do wyniku, a bieżący pozycja w nazwie źródła pozostaje niezmieniona.

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

  • *c- Dopasowuje wszystkie znaki źródłowe od bieżącej pozycji do ostatniego wystąpienia c(chciwe dopasowanie z rozróżnianiem wielkości liter) i dołącza dopasowany zestaw znaków do nazwy docelowej. Jeśli cnie zostanie znaleziony, wówczas dołączane są wszystkie pozostałe znaki ze źródła, a następnie c Jest to jedyna znana mi sytuacja, w której dopasowanie wzorca pliku Windows rozróżnia małe i wielkie litery.

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

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

  • .bez *naprzeciwko - Advances pozycję w źródle poprzez pierwszym wystąpieniu o .nie kopiowanie jakichkolwiek znaków, i dołącza .do nazwy docelowej. Jeśli .nie znaleziono go w źródle, następuje przejście do końca źródła i dopisanie .do nazwy celu.

Po wyczerpaniu docelowej maski, wszelkie końcowe .i {space}są przycinane na końcu wynikowej nazwy docelowej, ponieważ nazwy plików systemu Windows nie mogą kończyć się na .lub{space}

Kilka praktycznych przykładów

Zastąp znak na 1. i 3. pozycji przed dowolnym rozszerzeniem (dodaje 2 lub 3 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

Zmień (ostateczne) rozszerzenie każdego pliku

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

Dołącz rozszerzenie do każdego pliku

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

Usuń dodatkowe rozszerzenie po początkowym rozszerzeniu. Należy pamiętać, że ?należy zachować odpowiednią wartość, 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 odfiltruj pliki o początkowej nazwie i / lub rozszerzeniu dłuższym niż 5 znaków, aby nie zostały obcięte. (Oczywiście można dodać dodatkowy ?na każdym końcu 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 _nazwisku i spróbuj zachować rozszerzenie. (Nie działa poprawnie, jeśli _pojawia się 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)

Każda nazwa może być podzielona na komponenty, które są rozdzielane znakami. . Można je dodawać lub usuwać tylko na końcu każdego komponentu. Znaków nie można usuwać ani dodawać na początku lub w środku komponentu, zachowując pozostałą część za pomocą symboli wieloznacznych. Zastępstwa są dozwolone w dowolnym miejscu.

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, wówczas maska ​​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


Przydatne dziwactwo / błąd? do usuwania prefiksów nazw

W tym poście o SuperUser opisano, jak można użyć zestawu ukośników ( /), aby usunąć wiodące znaki z nazwy pliku. Jeden znak ukośnika jest wymagany do usunięcia każdego znaku. Potwierdziłem zachowanie na komputerze 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 podwójne cudzysłowy. Wszystkie poniższe formularze bez wymaganych cytatów kończą się 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

Nie /można go użyć do usunięcia jakichkolwiek znaków na środku lub na końcu nazwy pliku. Może usuwać tylko wiodące (przedrostki) znaki.

Technicznie /nie działa jak symbol wieloznaczny. Raczej wykonuje prostą zamianę znaków, ale następnie po zamianie komenda REN rozpoznaje, że /nie jest poprawna w nazwie pliku, i usuwa z niej wiodące /ukośniki. REN podaje błąd składniowy, jeśli wykryje /w środku nazwy celu.


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

Począwszy od pustego folderu testowego:

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 pasuje do długiej nazwy pliku, a nazwa pliku zmienia się na oczekiwany wynik 223456789.123.x. RENAME następnie szuka dalszych plików do przetworzenia i znajduje nowo nazwany plik za pomocą nowej krótkiej nazwy 223456~1.X. Nazwa pliku jest następnie ponownie zmieniana, co daje końcowy wynik 223456789.123.xx.

Jeśli wyłączę generowanie nazw 8.3, to RENAME daje oczekiwany wynik.

Nie w pełni opracowałem wszystkie warunki wyzwalające, które muszą istnieć, aby wywołać to dziwne zachowanie. Martwiłem się, że może być możliwe utworzenie niekończącej się rekurencyjnej RENAME, ale nigdy nie byłem w stanie jej wywołać.

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

  • Krótkie nazwy 8.3 muszą być włączone
  • SourceMask musi pasować do oryginalnej długiej nazwy.
  • Początkowa zmiana nazwy musi wygenerować krótką nazwę, która również pasuje do maski źródłowej
  • Początkowa krótka nazwa musi zostać posortowana później niż oryginalna krótka nazwa (jeśli istnieje?)
dbenham
źródło
6
Cóż za dokładna odpowiedź .. +1.
meder omuraliev
Niezwykle rozbudowany!
Andriy M
13
Na tej podstawie Microsoft powinien po prostu dodać „W celu uzyskania informacji zobacz superuser.com/a/475875 ” w REN /?.
efotinis
4
@CAD - Ta odpowiedź to w 100% oryginalna treść, którą Simon zamieścił na swojej stronie na moją prośbę. Spójrz na dół tej strony SS64, a zobaczysz, że Simon przypisuje mi pracę.
dbenham
2
@ JacksOnF1re - Dodano nowe informacje / technikę do mojej odpowiedzi. W rzeczywistości możesz usunąć swój Copy of prefiks, używając niejasnej techniki slash do przodu:ren "Copy of *.txt" "////////*"
dbenham
4

Podobnie jak exebook, oto implementacja C #, aby uzyskać docelową nazwę pliku z pliku źródłowego.

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

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

    }
amrunning
źródło
Dzięki za informację o błędzie w moim przykładzie. Zredagowałem swoją odpowiedź, aby to naprawić.
dbenham
1

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

Nie testowałem sourceMaskzbyt wiele, ale targetMaskpasuje 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)
}
exebook
źródło
0

Udało mi się napisać ten kod w języku BASIC, aby zamaskować nazwy plików symboli 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
Eoredson
źródło
4
Czy możesz wyjaśnić, w jaki sposób odpowiada to, o co pytano w pytaniu?
fixer1234
Replikuje funkcję REN do dopasowywania symboli wieloznacznych, taką jak przetwarzanie REN * .TMP * .DOC, w zależności od tego, jak funkcja jest wywoływana przed zmianą nazw plików.
eoredson