Centrum skryptów - Systemy operacyjne

Jak przenosić pliki na podstawie wartości znajdującej się w nazwie pliku? 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 przenosić pliki na podstawie wartości znajdującej się w nazwie pliku?

Cześć Skrypciarze! PytanieCześć, Skrypciarze! Mam kilka plików, które muszę przenieść do nowych folderów; to, do którego folderu ma zostać przeniesiony dany plik, zależy od roku – wartości pojawiającej się w nazwie pliku. Jak napisać skrypt, który przeniesie te pliki?

-- LP

Cześć Skrypciarze! OdpowiedźCzęść, LP. Może Wy znacie jakiś niezawodny sposób na brak pomysłów. Ja próbowałem już chyba wszystkiego. To trochę tak, jak z problemem z zaśnięciem. Im intensywniej się o tym myśli, tym trudniej osiągnąć upragniony efekt. Chciałem zabłysnąć dzisiaj jakimś świetnym spostrzeżeniem albo ciekawą historią, ale mam blokadę. Chyba powinienem przestać chcieć, ponieważ im bardziej chcę, tym większą mam blokadę i czuję, że nic z tego nie będzie. Zatem dzisiaj nie będzie żadnej anegdoty, na którą być może wielu naszych czytelników bardzo czeka. Naprawdę się starałem, nie chciałem nikogo rozczarować.

Nie chcę przedłużać, bo nie chcę, żeby ktoś pomyślał, że próbuję się jakoś usprawiedliwić i przejdę od razu do odpowiedzi na pytanie o skrypt przenoszący pliki w oparciu o nazwę pliku. Oto skrypt:

strComputer = "."



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



Set colFiles = objWMIService.ExecQuery _

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

        & "ResultClass = CIM_DataFile")



Set objRegEx = CreateObject("VBScript.RegExp")



For Each objFile in colFiles

    objRegEx.Global = True   

    objRegEx.Pattern = "\d{4}"



    strSearchString = objFile.FileName

    Set colMatches = objRegEx.Execute(strSearchString)



    strYear = colMatches(0).Value



    strNewFile = "C:\Test\" & strYear & "\" & objFile.FileName & _

        "." & objFile.Extension

    objFile.Copy(strNewFile)

    objFile.Delete

Next

Naprawdę długi ten skrypt. Ktoś chyba wstał dziś z łóżka lewą nogą!

Zanim przejdę do wyjaśnień dotyczących skryptu, skupmy się przez chwilę na plikach, z którymi mamy do czynienia. Zakładam, że mamy kilka plików o nazwach podobnych do poniższych:

File2005a.txt

File2006a.txt

File2006b.txt

File2006c.txt

File2006d.txt

File2007a.txt

File2007b.txt

Prawdę mówiąc, nie jest to aż tak istotne, jak te pliki wyglądają, poza dwoma wyjątkami: rok musi zostać określony za pomocą 4 cyfr i możemy mieć tylko jedną 4-cyfrową liczbę w nazwie. Po co nam ten drugi warunek? No cóż, przypuśćmy, że mamy nazwę pliku podobną do poniższej:

File2005_3000.txt

Mając nazwę File2005_3000.txt, tak naprawdę mamy dwie 4-cyfrowe liczby: 2005 oraz 3000. W takim przypadku, my prawdopodobnie możemy zgadnąć, która z tych liczb reprezentuje rok, ale przypuśćmy, że mamy nazwę pliku podobną do poniższej:

File1998_1999_2000.txt

Co teraz zrobimy? Najprawdopodobniej większość tego typu nazw opiera się na jakiejś konkretnej zasadzie, np. „Druga 4-cyfrowa liczba reprezentuje rok”. Ponieważ jednak nie znamy tej zasady, nie możemy wziąć pod uwagę każdej możliwości. Dlatego też pokażę jedynie, jak pobrać wszystkie 4-cyfrowe wartości; sami możecie określić, które z nich reprezentują rok.

Jeżeli chodzi o sam skrypt, rozpoczynamy od połączenia się z usługą WMI na lokalnym komputerze. Czy możliwe jest zastosowanie tego skryptu dla zdalnego komputera? Wiedziałem, że ktoś o to prędzej czy później zapyta, dlatego zdecydowałem się zastosować usługę WMI, a nie np. FileSystemObject. A skoro już stosujemy usługę WMI, odpowiedź brzmi: tak, możemy wykorzystać ten skrypt dla zdalnego komputera. Wystarczy tylko przypisać nazwę tego komputera do zmiennej o nazwie strComputer, w ten oto sposób:

strComputer = "atl-fs-01"

Po połączeniu się z usługą WMI, stosujemy ten oto dziwnie wyglądający wiersz kodu w celu pobrania kolekcji wszystkich plików znajdujących się w folderze C:\Test:

Set colFiles = objWMIService.ExecQuery _

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

        & "ResultClass = CIM_DataFile")

Jak już mówiłem, dziwnie on wygląda, ale taka jest natura kwerend Associators Of usługi WMI. Jednakże my teraz tylko pobieramy wszystkie pliki (wystąpienia klasy CIM_DataFile) które znajdują się (i które są jego skojarzeniami) w folderze C:\Test (Win32_Directory.Name='C:\Test'). Kiedy już mamy tę kolekcję, tworzymy wystąpienie obiektu VBScript.RegExp; w tym momencie możemy zająć się zarządzaniem plikami na poważnie.

Że co? Do czego służy nam obiekt VBScript.RegExp? No cóż. Ten obiekt umożliwi nam przeprowadzenie wyszukiwania wyrażeń regularnych w każdej nazwie pliku. Nie wiemy z góry, który rok (lub które lata) pojawią się w każdej nazwie pliku; może to być 1912, może to być 1987 lub 2008. Gdybyśmy chcieli, moglibyśmy utworzyć pozornie niekończącą się serię poleceń InStr, metodycznie wyszukujących każdą z następujących możliwości: 1913; 1914; 1915; 1916; 1917; itd. Alternatywnie moglibyśmy zastosować proste wyrażenie regularne w celu wyszukania wszystkich wartości składających się z 4 następujących po sobie cyfr. Hmmm, sam już nie wiem czy to dużo roboty, czy całkiem niewiele. Spróbujcie zgadnąć, którą z tych opcji wybierają Skrypciarze.

A skoro mowa o Skrypciarzach, za każdym razem, gdy otrzymujemy kolekcję wszystkich plików z folderu, nie możemy się oprzeć pokusie utworzenia pętli For Each i przejścia przez każdy plik znajdujący się w tej kolekcji. Dzisiejszy skrypt nie jest żadnym wyjątkiem. Zatem uruchamiamy pętlę For Each, która przejdzie przez każdy jeden plik znajdujący się w naszej kolekcji. Wewnątrz tej pętli przypisujemy wartości do dwóch właściwości naszego obiektu wyrażeń regularnych:

objRegEx.Global = True   

objRegEx.Pattern = "\d{4}"

Ustawiając wartość właściwości Global na True, nakazujemy skryptowi wyszukanie wszystkich wystąpień docelowego wzoru; jeżeli ta właściwość nie zostałaby ustawiona na True, skrypt odnalazłby pierwszą 4-cyfrową liczbę i zakończył wyszukiwanie. W naszym wypadku nie ma to znaczenia; w końcu nasze nazwy plików zawierają tylko jedną 4-cyfrową liczbę. Dorzuciliśmy tę informację ze względu na zgodność z zalecanymi procedurami. Niewiele to kosztuje, a satysfakcja jest niemała.

Jeżeli chodzi o właściwość Pattern, jest to po prostu wartość, której szukamy. Oczywiście nie znamy dokładnej wartości, której szukamy. Ale ponieważ używamy wyrażeń regularnych, nie musimy znać dokładnej wartości tylko szukamy określonego wzoru. (na przykład 4 następujących po sobie cyfr). \d{4} to po prostu składnia wyrażenia regularnego (j.ang.) dla 4 następujących po sobie cyfr.

Po przypisaniu wartości do właściwości dwóch wyrażeń regularnych, przypisujemy nazwę Name pierwszego pliku z kolekcji do zmiennej o nazwie strSearchSrting:

strSearchString = objFile.FileName

W tym momencie wywołujemy metodę Execute w celu wyszukania nazwy pliku dla wszystkich wystąpień naszego docelowego wzoru (4 następujących po sobie cyfr):

Set colMatches = objRegEx.Execute(strSearchString)

Zakładając, że znajdziemy docelowy wzór (czego jesteśmy pewnie w tym przypadku), te wystąpienia zostaną zachowane w kolekcji Matches wyrażeń regularnych. A ponieważ pierwsze takie wystąpienie (i tylko wystąpienie) będzie rokiem oraz ponieważ pierwsze takie wystąpienie otrzyma numer indeksu 0 oznacza to, że możemy pobrać bieżący rok (wartość dopasowania Value) za pomocą poniższego wiersza kodu:

strYear = colMatches(0).Value

Tak po prostu, rok pliku (a przynajmniej rok określony w nazwie pliku) zostanie zachowany w zmiennej o nazwie strYear.

Reszta jest prosta. Kiedy znamy już rok, możemy utworzyć całkiem nową ścieżkę stosując poniższe dwa wiersze kodu:

strNewFile = "C:\Test\" & strYear & "\" & objFile.FileName & _

    "." & objFile.Extension

W tym wierszu umieszczamy kolejno obok siebie następujące wartości:

  • C:\Test\
  • 2005 (rok pliku, zachowany w zmiennej strYear)
  • \ (w celu oddzielenia nazwy folderu od nazwy pliku)
  • File2005a (nazwa pliku, zachowana we właściwości FileName)
  • . (kropka wymagana do wytyczenia rozszerzenia pliku)
  • txt (rozszerzenie pliku, zachowane we właściwości Extension)

Złożone razem dadzą nam:

C:\Test\2005\File2005a.txt

Kluczowa część? Liczba 2005 znajdująca się w środku pliku; to folder (C:\Test\2005) do którego przeniesiemy ten plik.

Z jakiegoś dziwnego powodu usługa WMI nie posiada metody do przenoszenia plików. Dlatego też w celu przeniesienia plików musimy zastosować dwustopniowe podejście: najpierw kopiujemy oryginalny plik do nowego folderu (C:\Test\2005), a następnie usuwamy plik oryginalny (pozostawiając kopię File2005a.txt w folderze 2005). Do tego posłużą nam poniższe dwa wiersze kodu:

objFile.Copy(strNewFile)

objFile.Delete

Nie jest to może tak efektowne, jak stosowanie jednego polecenia Move, ale efekt jest ten sam, chociaż przy wykonaniu dodatkowej czynności.

Teraz wracamy na początek pętli i powtarzamy ten proces dla następnego pliku znajdującego się w kolekcji.

Uwaga. Powinienem chyba dodać, że ten skrypt zakłada, iż docelowe foldery (np folder 2005) już istnieją. Co robimy w przypadku, gdy jeszcze nie zostały utworzone? W takim przypadku zajrzyjcie do naszego archiwalnego artykułu, który zawiera więcej informacji na temat sprawdzania, czy folder istnieje, a w przypadku jego nieistnienia, sposobu jego utworzenia.

To, mamy nadzieję, powinno wystarczyć.

Jutro na pewno będzie lepiej. Postaram się wymyślić jakąś naprawdę wystrzałową anegdotę. Albo nie, w ogóle nie będę się starał. Wtedy na pewno mi się uda.

 Do początku strony Do początku strony

Centrum skryptów - Systemy operacyjne