Jak zastąpić nieprawidłowe daty w nazwach plików?
Skrypciarze odpowiadają na Wasze pytania
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! 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ść, 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 |