Centrum Skryptów - Systemy operacyjne

Jak wyodrębnić z ciągu trzycyfrowe liczby?

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 wyodrębnić z ciągu trzycyfrowe liczby?

Cześć Skrypciarze! Pytanie

Cześć, Skrypciarze! Próbuję zastosować wyrażenia regularne do wyodrębnienia trzycyfrowych liczb otoczonych pionowymi kreskami. Są one osadzone w wartościach ciągu takich, jak: |159|468|572|843|. Nie mogę jednak rozpracować składni, która rozwiązałaby to zadanie. Możecie mi pomóc?

-- RS

Cześć Skrypciarze! Pytanie

Cześć, RS. Zanim zajmę się odpowiadaniem na Twoje pytanie, chciałbym zauważyć, że Zimowa Olimpiada Skrypciarska 2008 (j.ang.) rozpoczęła się już na dobre: od piątku dostępne są wszystkie główne konkurencje, a teraz możecie zapoznać się z pierwszą konkurencją w kategorii nagłej śmierci (j.ang.).

Jak już mówiłem w poprzednich artykułach, pilnujcie terminów. Nie panikujcie, ale bądźcie czujni. Do zdobycia naprawdę fajne nagrody.

Uwaga: Jedynymi osobami, które powinny panikować są Skrypciarze. Olimpiada trwa dopiero kilka dni, od piątku będącego zazwyczaj bardzo spokojnym dniem w Centrum skryptów. Już teraz wiemy, że będziemy mieli więcej pracy w związku z Olimpiadą, niż rok temu. Czy to jakiś problem? Ujmę to tak: w zeszłym roku ledwo daliśmy radę, a w tym roku… Lepiej nie zastanawiać się, co będzie w tym roku. Nie dlatego, że nie chcę się zastanawiać, ale dlatego, że nie mam na to czasu. Dopiero środek tygodnia, a ja już padam z nóg.

Tymczasem, kiedy wszyscy Skrypciarze odchodzą od zmysłów, uczestnicy Olimpiady mają mnóstwo czasu. Przynajmniej wystarczająco dużo, żeby oderwać się na kilka minut i poznać sposób wyodrębniania trzycyfrowych liczb z ciągu. Oto on:

Set objRegEx = CreateObject("VBScript.RegExp")



objRegEx.Global = True   

objRegEx.Pattern = "\d{3}"



strSearchString = "|159|468|572|843|"



Set colMatches = objRegEx.Execute(strSearchString)  



For Each strMatch in colMatches

    Wscript.Echo strMatch.Value

Next

Jak się okazuje, RS, Twój problem mógł wynikać z tego, że zastosowany został nadmiernie skomplikowany scenariusz. (A wierzcie mi, że jeżeli jest ktoś, kto potrafi rozpoznać sytuację, w której ktoś niepotrzebnie komplikuje sobie życie, to tym kimś jestem ja.). Scenariusz zastosowany przez RS obejmował uwzględnienie kresek pionowych w wyrażeniu regularnym. Czy to może stanowić problem? Może, ponieważ znak kreski pionowej jest w wyrażeniach regularnych znakiem zarezerwowanym. Oznacza to, że nie można go tak po prostu dołączyć do podawanego wzorca. Jak się zresztą okazało, poniższy wzorzec wyrażenia regularnego nie przekaże nam oczekiwanych danych:

objRegEx.Pattern = "|...|"

Nie jest to jednak powód do zmartwień, jeżeli chodzi o znak kreski. Patrząc na przykładowe dane, które otrzymałem, sądzę, że chodzi wyłącznie o trzycyfrowe liczby. Kreski pionowe nie mają znaczenia. Te trzycyfrowe liczby mogłyby być oddzielone za pomocą spacji, przecinków, a nawet liter:

aaaaa159bbbbb468ccccc572ddddd843eeeee

Innymi słowy, wystarczy wyszukać trzycyfrowe liczby i praca skończona.

Uwaga: Technicznie rzecz biorąc nie jest nam potrzebne wyrażenie regularne. Moglibyśmy zastosować funkcję Split i podzielić ciąg, używając kreski pionowej jako delimitera. Dlaczego tego nie zrobiliśmy? No cóż, w tym przypadku otrzymalibyśmy tablicę zawierającą pusty element na początku i na końcu. Chcąc uniknąć zajmowania się tą kwestią, zdecydowałem, że lepiej będzie zmodyfikować wyrażenie regularne zastosowane przez RS.

Jak wyszukać liczby trzycyfrowe? Cóż, w wierszu 1 tworzymy wystąpienie obiektu VBScript.RegExp. Jest to obiekt, który umożliwia nam używanie wyrażeń regularnych w skryptach VBScript. Po jego utworzeniu RegExp przypisujemy wartości dwóm kluczowym właściwościom obiektu:

  • Global. Właściwość określająca, czy skrypt powinien odnaleźć tylko jedno wystąpienie tekstu docelowego, czy też wszystkie wystąpienia tego tekstu. Ponieważ chcemy znaleźć wszystkie trzycyfrowe liczby, ustawiamy wartość tej właściwości na True.
  • Pattern. Właściwość reprezentująca tekst docelowy. W tym miejscu określamy dokładnie to, czego szukamy. Chcemy znaleźć liczby trzycyfrowe, więc używamy składni \d{3}. Wartość \d oznacza, że szukamy cyfry (liczby z zakresu od 1 do 9). Konieczne jest użycie znaku \, ponieważ „d” także oznacza znak zarezerwowany w wyrażeniach regularnych. {3} informuje skrypt, że chcemy znaleźć dokładnie trzy takie znaki (cyfry z zakresu od 1 do 9). Ani mniej, ani więcej.

Po przypisaniu naszego ciągu wyszukiwania do zmiennej o nazwie strSearchString wywołujemy metodę Execute w celu wyszukania w tym ciągu tekstu docelowego:

Set colMatches = objRegEx.Execute(strSearchString)

Wszelkie elementy pasujące, znalezione przez obiekt RegExp zostaną zachowane w kolekcji o nazwie colMatches. Aby wyświetlić listę liczb trzycyfrowych wystarczy uruchomić pętlę For Each, która przejdzie przez elementy znajdujące się w kolekcji i dla każdego z nich wywoła echo właściwości Value:

For Each strMatch in colMatches

    Wscript.Echo strMatch.Value

Next

Po wykonaniu tej czynności powinniśmy otrzymać następujący wynik:

159

468

572

843

Czyli dokładnie to, o co nam chodziło.

Zauważmy, że to wyrażenie regularne działa w przypadku danych otrzymanych od RS, ale niekoniecznie zadziała w przypadku innych danych. Załóżmy, że mamy następujący ciąg:

123aaaa|159|468|572|843|aaaa456

Ten ciąg poprawnie odnajdzie wartości 159, 468, 572 oraz 843; znajdzie jednak również wartość 123 oraz 456. Dlaczego? Ponieważ wyszukuje on po prostu wszelkie trzycyfrowe wartości, niezależnie od tego, czy są ujęte pomiędzy kreskami pionowymi.

Szczerze mówiąc, moglibyśmy tworzyć coraz bardziej skomplikowane scenariusze, wymagające coraz bardziej skomplikowanych wyrażeń regularnych. Pokażę wam jednak, co zrobić z tym problemem. Oto skrypt, który wyodrębni tylko te liczby trzycyfrowe, które „zaczynają się” kreską pionową:

Set objRegEx = CreateObject("VBScript.RegExp")



objRegEx.Global = True   

objRegEx.Pattern = "\|\d{3}"



strSearchString = "123aaaa|159|468|572|843|aaaa456"



Set colMatches = objRegEx.Execute(strSearchString)  



For Each strMatch in colMatches  

    strValue = strMatch.Value

    strValue = Replace(strValue, "|", "") 

    Wscript.Echo strValue

Next

Aby ten skrypt mógł zadziałać, musieliśmy wprowadzić kilka modyfikacji. Najpierw zmieniliśmy wzorzec na poniższy:

objRegEx.Pattern = "\|\d{3}"

W dalszym ciągu szukamy liczb trzycyfrowych; jednak te liczby muszą być poprzedzone znakiem |. (Jak już mówiłem, ponieważ kreska pionowa jest znakiem zarezerwowanym, musi zostać oznaczona znakiem \.). Za pomocą tego wyrażenia regularnego nie znajdziemy liczby 123 znajdującej się na początku ciągu. Dlaczego? Otóż to: ta liczba nie została poprzedzona kreską pionową.

Dobre pytanie: czy nie byłoby lepiej wyszukać wartości trzycyfrowych otoczonych z dwóch stron kreskami pionowymi? Tak, byłoby. Jednakże w tej opcji pojawia się pewien problem. Przypuśćmy, że znajdziemy wartość |159|. Całkiem w porządku, poza tym, że użyta została kończąca kreska |. Oznacza to, że nie znajdziemy wartości |468|; według skryptu wartość |468| nie istnieje. Skrypt jest natomiast przekonany, że w ciągu znajdują się takie elementy (pomijając wszystkie elementy zewnętrzne):

  • |159|
  • 468
  • |572|
  • 843|

Czy jest jakiś sposób, żeby to obejść? Tak, ale wykracza on poza to, co możemy dzisiaj opisać. Zresztą także poza to, czego potrzebuje RS.

Kolejną rzeczą, którą zmodyfikowaliśmy, była pętla For Each. Ponieważ znak kreski pionowej jest częścią naszego wzorca. Oznacza to, że znak kreski pionowej będzie częścią wartości, które odnajdziemy. Domyślnie skrypt przekazuje nam co następuje:

|159

|468

|572

|843

Aby pozbyć się tych kresek pionowych, zachowaliśmy właściwość Value dla każdego elementu w zmiennej o nazwie strValue, a następnie zastosowaliśmy funkcję Replace w celu usunięcia wszystkich wystąpień kreski pionowej:

strValue = Replace(strValue, "|", "")

Teraz wywołujemy echo wartości zmiennej strValue i uzyskujemy następujący wynik:

159

468

572

843

Teraz już lepiej, nieprawdaż?

Jak już mówiłem, moglibyśmy znaleźć sposób na wykorzystanie także tego wyrażenia regularnego. Jednak to, co już mamy, powinno wystarczyć.

A co mi tam, zróbmy jeszcze jeden. (Wiecie, jak to jest ze skryptowaniem: jak raz się zacznie, nie można przestać.) Oto poprawiony skrypt, który jest może łatwiejszy w obsłudze niż ten pokazany poprzednio:

Set objRegEx = CreateObject("VBScript.RegExp")



objRegEx.Global = True   

objRegEx.Pattern = "\|\d{3}(?=\|)"



strSearchString = "12|3aaaa|159333|468|572|843|aaaa|456"



Set colMatches = objRegEx.Execute(strSearchString)  



For Each strMatch in colMatches  

    strValue = strMatch.Value

    strValue = Replace(strValue, "|", "") 

    Wscript.Echo strValue

Next

Zwróćcie uwagę na wzorzec, który tutaj stosujemy. Dodaliśmy na końcu tę konstrukcję: (?=\|). Jest to składnia „dodatniej wyprzedzającej”: dzięki niej skrypt szuka znaku kreski pionowej następującego po liczbie trzycyfrowej. Informuje ona także skrypt, że ten znak nie powinien zostać uwzględniony w końcowej otrzymanej wartości; w ten sposób otrzymujemy wynik |468 zamiast |468|. Dzięki temu znak kreski pionowej zostaje uwolniony i może stanowić część następnego wyszukiwania.

Jak już mówiłem, sprawy się komplikują i nie chcę zanudzać Was szczegółami, przynajmniej nie dzisiaj.

To ostatnie podejście dotyczące dodatniej wyprzedzającej ma jedną przewagę nad skryptem wyszukującym po prostu znaku kreski pionowej z następującymi po niej trzema cyframi. Tamtej skrypt uznawał wartość |159 jako pasującą. Dlaczego? Ponieważ zawiera kreskę pionową i następujące po niej trzy cyfry. Oczywiście wartość 159 to tak naprawdę tylko część 159333. Dlatego też nie należy jej uwzględniać w żadnych wynikach. Tak czy inaczej nasze wyrażenie regularne robi, co mu każemy: wyszukuje znaków kreski pionowej z następującymi po nim trzema cyframi i przekazuje nam znalezione wartości.

Dodając ostatnie podejście unikamy tego problemu; teraz te trzy cyfry muszą zarówno być poprzedzone kreską, jak i mieć kreskę po sobie. Jeżeli tak nie jest, nie zostaną

12|3aaaa|159333|468|572|843|aaaa|456

Mając taki ciąg otrzymamy następujące wartości:

468

572

843

Teraz dopiero jest dobrze.

 Do początku strony Do początku strony

Centrum Skryptów - Systemy operacyjne