Centrum Skryptów - Systemy operacyjne

Jak zastąpić nieprawidłowe daty w nazwach plików?

Udostępnij na: Facebook

Skrypciarze odpowiadają na Wasze pytania

Cześć Skrypciarze!

Witamy w rubryce TechNet, w której Skrypciarze z firmy Microsoft odpowiadają na częste pytania dotyczące używania skryptów w administracji systemu. Jeśli macie jakieś pytania z tej dziedziny, zachęcamy do wysłania e-maila na adres: scripter@microsoft.com. Nie możemy zagwarantować odpowiedzi na każde otrzymane pytanie, ale staramy się jak możemy.

Jak zastąpić nieprawidłowe daty w nazwach plików?

Cześć Skrypciarze! Pytanie

Cześć, Skrypciarze! Mam folder, zawierający wiele plików. Część z tych plików ma nazwę zawierającą nieprawidłową datę; przykładowo plik może nosić nazwę FGA Site Visit 2-14-07 204.jpg, podczas gdy powinien nosić nazwę FGA Site Visit 2-14-08 204.jpg. (Czyli odnosić się do roku 2008, a nie 2007.). Jak zastąpić te nieprawidłowe daty prawidłowymi?

-- SM

Cześć Skrypciarze! Pytanie

Cześć, SM. Stali czytelnicy rubryki Cześć Skrypciarze – macie rację: określenie jest co najmniej dziwne. W końcu, któż nie jest stałym czytelnikiem rubryki Cześć Skrypciarze? Prawdę mówiąc nikt taki nie przychodzi mi na myśl. Fidel Castro? Nie, Fidel pisze kilka razy w miesiącu z zapytaniem, jak zarządzać serwerem DHCP za pomocą usługi WMI.

I kilka razy w miesiącu odpisujemy mu, że nie ma klasy WMI zarządzającej serwerem DHCP. A Fidel dalej pisze.

W każdym razie stali czytelnicy rubryki Cześć Skrypciarze wiedzą, że zazwyczaj zaczynamy od nudnej anegdoty, opowiadając o wszystkim o tym, co nikogo tak naprawdę nie obchodzi, sypiąc surowymi dowcipami. A dopiero kiedy pomysły nam się wyczerpują, zabieramy się do odpowiedzi na pytanie. Nie znoszę sytuacji, kiedy muszę kogoś rozczarować, zwłaszcza stałych czytelników, ale w dzisiejszym artykule nie będzie nic z tych rzeczy, bo mam straszne urwanie głowy z Zimową Olimpadą Skrypciarską 2008 (j.ang.), a zaległości wydają się mnożyć z godziny na godzinę. Zatem żadnych anegdot w dniu dzisiejszym, przejdę od razu do rzeczy.

Z życia wzięte: Ogromna lista zaległości związanych z Olimpiadą Skrypciarską oznacza, że po raz pierwszy w życiu moja lista rzeczy do zrobienia w pracy jest dłuższa niż lista rzeczy do zrobienia w domu. A lista rzeczy do zrobienia w domu ma 13-letnią historię.

Właśnie: miałem skupić się na odpowiedzi na pytanie, nieprawdaż? Skoro tak powiedziałem, to tak zrobię:

Set objRegEx = CreateObject("VBScript.RegExp")



objRegEx.Global = True   

objRegEx.Pattern = "(\d{1,2})-(\d{1,2})-07"



strComputer = "."



Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")



Set colFileList = objWMIService.ExecQuery _

    ("ASSOCIATORS OF {Win32_Directory.Name='C:\Temp'} Where " _

        & "ResultClass = CIM_DataFile")



For Each objFile In colFileList

    strOldName = objFile.Name

    strNewName = objRegEx.Replace(strOldName,"$1-$2-08")

    If strNewName <> strOldName Then  

        errResult = objFile.Rename(strNewName)

    End If

Next

Zanim zagłębię się w szczegóły na temat działania tego skryptu, chciałbym zauważyć, że postanowiłem zastosować wyrażenie regularne w celu zmiany nazwy tych plików. Na pierwszy rzut oka wydaje się to lekką przesadą. W końcu czy nie można po prostu wyszukać w każdej nazwie wartości 07 i zastąpić ją wartością 08? Cóż, być może można. Ale z drugiej strony lepiej tego nie robić. Dla przykładu załóżmy, że nasz folder (C:\Temp) zawiera następujący zbiór plików:

FGA site visit 1-14-07 204 .vbs

FGA site visit 2-14-07 205.txt

FGA site visit 2-20-07 206.txt

07 DebugMe.ps1

DebugMe-07.pl

Jak widzimy tylko trzy pierwsze pliki zawierają jedną z naszych nieprawidłowych wartości dat. Pozostałe dwa pliki zawierają wartość 07, ale nie jako datę. To oznacza, że nie chcemy zastępować wartości 07 w pliku DeBugMe-07.pl; ten plik powinien mieć nazwę DeBugMe-07.pl, a nie DeBugMe-08.pl. A jednak nazwa ta zostałaby zmieniona, gdybyśmy zastosowali wyszukiwanie zbiorcze i zastąpili wszystkie wystąpienia wartości 07.

Dzięki zastosowaniu wyrażenia regularnego możemy uniknąć takiej sytuacji. Za pomocą wyrażenia regularnego możemy — i tak też zrobimy — wyszukać jedną lub dwie liczby z następującym po nich łącznikiem, z następującą po nich jedną lub dwiema cyframi i kolejnym łącznikiem i z następującą w końcu wartością 07. Nie jest to podejście niezawodne; na przykład mogłoby spowodować zamianę wartości pliku o nazwie Bob24-99-07.txt, mimo że wartość 24-99-07 także nie jest datą.

Może w kalendarzu Majów… Ale nie w kalendarzu gregoriańskim.

Uwaga: Czy kalendarz gregoriański został tak nazwany na cześć Skrypciarza Grega Stempa? Zaskoczę Was, ale nie. Nazwa pochodzi od nazwiska naszej Redaktorki Jean Ross. Ktoś się po prostu pomylił.

W każdym razie nasze wyrażenie regularne nie jest niezawodne. Ale i tak jest w wiele bezpieczniejsze niż wyszukiwanie i zastępowanie wszystkich wystąpień wartości 07.

Uwaga: Tak, teoretycznie można utworzyć wyrażenie regularne, które będzie skuteczniej odróżniać daty od wartości niebędących datami. Ale utworzenie takiego wyrażenia regularnego przekracza moje dzisiejsze możliwości.

Zatem jak używamy wyrażenia regularnego w naszym skrypcie? Cóż, zaczynamy od utworzenia wystąpienia obiektu VBScript.RegExp, który w ogóle umożliwia stosowanie wyrażeń regularnych. Po utworzeniu tego obiektu przypisujemy wartości do dwóch właściwości tego obiektu.

Właściwość Global określa, czy skrypt wyszukuje wszystkie wystąpienia docelowego ciągu lub tylko pierwsze wystąpienie. W przypadku tego skryptu nie ma to znaczenia; jednakże bardzo często konieczne będzie wyszukanie wszystkich wystąpień docelowego ciągu. Dlatego też ustawiamy wartość tej właściwości na True.

Właściwość Pattern reprezentuje tekst docelowy – wartość, której szukamy. W tym przypadku zaczynamy od wyszukania co najmniej 1, ale nie więcej niż 2 cyfr; do tego służy nam poniższa konstrukcja:

(\d{1,2})

Po cyfrach następuje łącznik (-) z następującymi po nim 1 lub 2 dodatkowymi cyframi oraz jeszcze jednym łącznikiem:

(\d{1,2})-(\d{1,2})-

Następne, po tych wartościach mamy ustaloną wartość 07:

(\d{1,2})-(\d{1,2})-07
Uwaga: Jeżeli ta składnia przyprawia Was o zawrót głowy, zajrzyjcie do naszego artykułu z Magazynu TechNet (j.ang.), który opisuje wykorzystanie wyrażeń regularnych.

Po skonfigurowaniu naszego obiektu wyrażeń regularnych łączymy się z usługą WMI na lokalnym komputerze. (Tak, można uruchomić ten skrypt na zdalnym komputerze; wystarczy przypisać jego nazwę do zmiennej strComputer.). Po utworzeniu połączenia używamy poniższego wiersza kodu w celu pobrania kolekcji wszystkich plików znajdujących się w folderze C:\Temp:

Set colFileList = objWMIService.ExecQuery _

    ("ASSOCIATORS OF {Win32_Directory.Name='C:\Temp'} Where " _

        & "ResultClass = CIM_DataFile")

W tym momencie możemy rozpocząć zmienianie nazw plików. W tym celu najpierw uruchamiamy pętlę For Each, która przejdzie przez wszystkie pliki w kolekcji. Wewnątrz tej pętli najpierw pobieramy wartość właściwości Name pliku (ekwiwalent ścieżki w usłudze WMI), przypisując ją wartość do zmiennej o nazwie strOldName:

strOldName = objFile.Name

Teraz możemy wykonać operację znajdź-i-zamień za pomocą wyrażenia regularnego. Do tego posłuży nam poniższy wiersz kodu:

strNewName = objRegEx.Replace(strOldName,"$1-$2-08")

Przyznaję, wygląda on trochę dziwnie, ale wyszuka docelowy tekst w ścieżce pliku (dwie cyfry, łącznik, dwie cyfry, łącznik oraz wartość 07). Co ważniejsze, zastąpi on wartość 07 wartością 08, pozostawiając nietknięte wartości reprezentujące miesiąc i dzień. Do tego służy $1 oraz $2.

Cieszę się, że to pytanie padło. $1, $2 oraz $3 to przykłady „odniesienia wstecznego” wyrażenia regularnego. Odniesienie wsteczne to nic więcej, niż tylko część wyszukanego tekstu, która może zostać zapisana i ponownie wykorzystana. W naszym skrypcie szukamy dwóch „podrzędnych dopasowań”:

  • Zestawu 2 cyfr.
  • Zestawu kolejnych 2 cyfr.

Każdemu z tych podrzędnych dopasowań automatycznie przypisywane jest odniesienie wsteczne: pierwsze dopasowanie podrzędne to $1; drugie $2; itd., aż dotrzemy do $9. Innymi słowy, załóżmy, że mamy datę 1-14-07. w naszym przypadku wyrażenie automatyczne przypisze następujące odniesienia wsteczne:

Cześć daty

Odniesienie wsteczne

1

$1

14

$2

W naszym ciągu zastępującym używamy tych odniesień wstecznych w celu zagwarantowania, że poprawne wartości miesiąca i dnia zostaną użyte ponownie. Według naszego tekstu zastępującego: po naszym pierwszym odniesieniu wstecznym ($1) zostaje dodany łącznik. Następnie, wstawiamy drugie odniesienie wsteczne ($2) z następującym po nim kolejnym łącznikiem. Na koniec dołączamy wartość 08.

Co dzięki temu otrzymamy? Ścieżkę pliku podobną do poniższej:

FGA site visit 1-14-08 204 .vbs

Po uruchomieniu wyrażenia regularnego sprawdzamy, czy nowa ścieżka pliku jest taka sama, jak starsza ścieżka pliku.

If strNewName <> strOldName Then

Co się dzieje, jeżeli te ścieżki są takie same? To oznacza po prostu, że w nazwie pliku nie występuje wartość daty, jak również że nie ma powodu, by zmieniać nazwę pliku. Mając to na uwadze wracamy na początek pętli For Each i próbujemy jeszcze raz z następnym plikiem w kolekcji.

Jak się zapewne domyśliliście, jeżeli ścieżki się różnią oznacza to, że musimy zmienić nazwę pliku. Możemy to zrobić za pomocą poniższego wiersza pliku oraz metody Rename:

errResult = objFile.Rename(strNewName)

Następnie wracamy na początek pętli, gdzie powtarzamy cały proces dla następnego pliku w kolekcji.

Po wykonaniu wszystkich opisanych czynności, nasz folder powinien zawierać następujące pliki:

FGA site visit 1-14-08 204 .vbs

FGA site visit 2-14-08 205.txt

FGA site visit 2-20-08 206.txt

07 DebugMe.ps1

DebugMe-07.pl

Dobra robota.

To powinno wystarczyć, SM; jeżeli nie, daj mi znać. Dopiszę to do listy rzeczy do zrobienia i jest szansa, że wrócimy do tematu za kilka miesięcy.

 Do początku strony Do początku strony

Centrum Skryptów - Systemy operacyjne