Generowanie, diagnostyka błędów i analiza semantyczna wyrażeń regularnych przy pomocy narzędzi skryptowych

Wyrażenia regularne to ‘chleb powszedni’ każdego analityka zdarzeń rejestrowanych w systemach klasy SIEM (Security Information and Event Management). Jednak od pozyskania logów ze źródła zdarzeń do ich analizy i korelacji prowadzi dość długa i – bywa że – nie całkiem łatwa ścieżka. Na każdym z etapów tej ścieżki znajomość wyrażeń regularnych stanowi kanon podstaw wiedzy o niezbędnych narzędziach.

Na saruby_regexpm początek trzeba system „nakarmić” wartościową informacją w postaci odpowiednio zdekomponowanych na czynniki pierwsze logów. Ta dekompozycja lub – jak można ją inaczej nazwać używając slangu branżowego – ich „parsowanie” opiera się właśnie na wyrażeniach regularnych. Potem przystępujemy do dalszych kroków przetwarzania zawartych w logach informacji. Kiedy wreszcie uda się wyekstrahować i uporządkować dane zapisane w logach, możemy w dalszej kolejności zabrać się za coś, co stanowi cel i sens działania SIEM-a, czyli korelację zdarzeń pochodzących z różnych źródeł. Tutaj także niezbędna jest znajomość wyrażeń regularnych.

Tym razem jednak to nie cyklowi przetwarzania zdarzeń chciałbym poświęcić ten artykuł. Zamiast tego sugeruję skupić się na narzędziach, które towarzyszą pracy z logami na każdym z jej etapów.  Jak zwykle będą to narzędzia skryptowe, których kod źródłowy towarzyszy tekstowi artykułu.

Dodam jedynie na początek, że w jednym z kolejnych artykułów mam zamiar napisać krótki przewodnik po wyrażeniach regularnych, więc tym razem czuję się w pełni usprawiedliwiony pomijając wstęp o podstawach wiedzy na ten temat.

Pierwsze z dwóch narzędzi to REGEXP Inspector. Narzędzie to zostało wyposażone z graficzny interfejs użytkownika opracowany z wykorzystaniem biblioteki GTK. Trzy zakładki jego GUI zapewniają dostęp do trzech funkcjonalności skryptu – (1) budowy wyrażeń regularnych o dowolnym poziomie złożoności, (2) inspekcji (kontroli semantycznej) podanego wyrażenia regularnego i (3) uzyskania podpowiedzi odnośnie składni wyrażeń regularnych.

Kolejne z zaprezentowanych narzędzi do prosty skrypt (REGEXP Processor) uruchamiany z linii poleceń, służący do wyszukiwania dopasowań do wyrażeń regularnych w określonych, wskazanych plikach tekstowych lub w standardowym strumieniu wejściowym (STDIN). Skrypt może być wywołany w trybie potokowym i tym samym może wydajnie przetwarzać dużych rozmiarów wolumeny danych bez nadmiernej konsumpcji zasobów pamięci operacyjnej.

Czym ten skrypt różni się na przykład od wszystkim dobrze znanego grepa? Otóż tym, że oprócz dopasować całkowitych, potrafi znajdować dopasowania częściowe do zgrupowanych przy pomocy operatorów grupowania (czyli zwykłych nawiasów) części (tokenów) wyrażenia regularnego. Jest wyposażony także w funkcjonalność inspekcji  czyli weryfikacji semantycznej wyrażenia regularnego. Przydaje się ona w przypadku, kiedy trzeba zdiagnozować błąd w wyrażeniu regularnym, co nie jest wcale zjawiskiem rzadkim, zwłaszcza kiedy budujemy wyrażenia regularne o dużej złożoności. Dodatkowo, opcja podpowiedzi (-h lub –help) umożliwia uzyskanie pomocy przy tworzeniu, interpretacji i diagnostyce REGEXP-ów.

Oba skrypty bazują na bardzo dobrych bibliotekach do przetwarzania wyrażeń regularnych: ‘regularity’ i ‘regexp_parser’. Trzeba rzecz jasna pamiętać, aby wcześniej zainstalować je w systemie:

gem install regularity
gem install regexp_parser

Jak zwykle gorąco Was zachęcam eksperymentowania z kodem i dalszego rozwoju skryptów. Możliwości jest oczywiście całkiem sporo. Można na przykład pokusić się o dopisanie kodu do obliczania i prezentacji statystyk dopasowań, do wizualizacji miejsc wystąpienia dopasowania w tekście, do ekstrakcji pasujących do wyrażeń regularnych wierszy czy bloków tekstu, do podmiany tekstów, do anonimizacji danych etc.

Życzę Wam miłej zabawy i zapraszam ponownie.

Poprawna składnia wywołania „REGEXP Processor”

/home/janusz/skrypty $ regexp_processor.rb
REGEXP inspector and processor by Janusz Nawrat (2014)
======================================================================
Usage: ruby regexp_processor.rb -r REGEXP -o operation [-f INPUT_FILE]

where:
        -r or --regexp should be the REGEXP string to inspect
           or process
        -f or --filename - optional parameter - the name of input
           data file. If not set, STDIN will be assumed instead
        -o or --operation should be 'inspect' or 'process'
        -h for getting help
======================================================================

Przykładowe użycie w trybie ‚PROCESS’

/home/janusz/skrypty $ regexp_processor.rb -o proc -r "(^def\s+\w{3,})|(require)(if)" < regexp_inspector.rb
PARTIAL MATCH (2)(require): require 'regexp_parser'
PARTIAL MATCH (3)(require): require 'optparse'
MATCH (5): def banner
MATCH (19): def help()
PARTIAL MATCH (25)(if): /./m - Any character (the m modifier enables multiline mode)
MATCH (118): def inspection(regular)
MATCH (126): def process(position, regular, string)
PARTIAL MATCH (130)(if):        if string.match(/#{regular}/)
PARTIAL MATCH (136)(if):                        if string.match(/#{elem}/)
PARTIAL MATCH (173)(if): if options[:operation] == nil
PARTIAL MATCH (181)(if): if options[:regexp] == nil
PARTIAL MATCH (186)(if): if options[:filename] == nil
PARTIAL MATCH (188)(if): elsif not File::exist?(options[:filename])
PARTIAL MATCH (202)(if):                if input_file != nil
____________________________________________________________
Number of full matches: 4, partial matches: 10

Przykładowe użycie w trybie ‚INSPECT’

/home/janusz/skrypty $ regexp_processor.rb -o ins -r "(^def\s+\w{3,})|(require)(if)"
Regular expression: (^def\s+\w{3,})|(require)(if) inspection:
type: group, token: capture, text: '(' [0..1]
type: anchor, token: bol, text: '^' [1..2]
type: literal, token: literal, text: 'def' [2..5]
type: type, token: space, text: '\s' [5..7]
type: quantifier, token: one_or_more, text: '+' [7..8]
type: type, token: word, text: '\w' [8..10]
type: quantifier, token: interval, text: '{3,}' [10..14]
type: group, token: close, text: ')' [14..15]
type: meta, token: alternation, text: '|' [15..16]
type: group, token: capture, text: '(' [16..17]
type: literal, token: literal, text: 'require' [17..24]
type: group, token: close, text: ')' [24..25]
type: group, token: capture, text: '(' [25..26]
type: literal, token: literal, text: 'if' [26..28]
type: group, token: close, text: ')' [28..29]

Interfejs graficzny programu REGEXP Inspector – zakładka generatora wyrażeń regularnych

regexp_inspector_01

Interfejs graficzny programu REGEXP Inspector – zakładka inspektora wyrażeń regularnych

regexp_inspector_02

Interfejs graficzny programu REGEXP Inspector – zakładka pomocy

regexp_inspector_03

Kod generatora i inspektora wyrażeń regularnych:

require 'gtk2'
require 'regexp_parser'
require 'regularity'

def message(parent, title, msg_text)
   dialog = Gtk::MessageDialog.new(
      parent,
      Gtk::Dialog::MODAL,
      Gtk::MessageDialog::INFO,
      Gtk::MessageDialog::BUTTONS_OK,
      msg_text
   )
   dialog.title = title
   dialog.run
   dialog.destroy
end

window = Gtk::Window.new("REGEXP inspector - Janusz Nawrat (2014)")
window.signal_connect("destroy") do
  Gtk.main_quit
end

window.border_width = 0
window.set_size_request(500, -1)

main_nb = Gtk::Notebook.new
gener_label = Gtk::Label.new("REGEXP generation")
inspect_label = Gtk::Label.new("REGEXP inspection")
help_label = Gtk::Label.new("HELP")

# ---------- Widgets to put into gener_box ------------

gener_box = Gtk::VBox.new()
gener_box.border_width = 10

regul = Regularity.new()

gener_container = Gtk::HBox.new()
button_box = Gtk::VBox.new()
pos_entry_box = Gtk::VBox.new()
entry_box = Gtk::VBox.new()

separator1 = Gtk::HSeparator.new(); separator2 = Gtk::HSeparator.new()

button_01 = Gtk::Button.new("Start with"); button_02 = Gtk::Button.new("Append")
button_03 = Gtk::Button.new("End with"); button_04 = Gtk::Button.new("Maybe")
button_05 = Gtk::Button.new("One of"); button_06 = Gtk::Button.new("Between")
button_07 = Gtk::Button.new("Zero or more"); button_08 = Gtk::Button.new("One or more")
button_09 = Gtk::Button.new("At east"); button_10 = Gtk::Button.new("At most")

pos_entry_01 = Gtk::Entry.new(); pos_entry_02 = Gtk::Entry.new();
pos_entry_03 = Gtk::Entry.new(); pos_entry_04 = Gtk::Entry.new();
pos_entry_05 = Gtk::Entry.new(); pos_entry_06 = Gtk::Entry.new();
pos_entry_07 = Gtk::Entry.new(); pos_entry_08 = Gtk::Entry.new();
pos_entry_09 = Gtk::Entry.new(); pos_entry_10 = Gtk::Entry.new();

pos_entry_04.sensitive = false
pos_entry_05.sensitive = false
pos_entry_07.sensitive = false
pos_entry_08.sensitive = false

entry_01 = Gtk::Entry.new(); entry_02 = Gtk::Entry.new()
entry_03 = Gtk::Entry.new(); entry_04 = Gtk::Entry.new()
entry_05 = Gtk::Entry.new(); entry_06 = Gtk::Entry.new()
entry_07 = Gtk::Entry.new(); entry_08 = Gtk::Entry.new()
entry_09 = Gtk::Entry.new(); entry_10 = Gtk::Entry.new()

button_box.pack_start(button_01, true, true, 1); button_box.pack_start(button_02, true, true, 1); button_box.pack_start(button_03, true, true, 1)
button_box.pack_start(button_04, true, true, 1); button_box.pack_start(button_05, true, true, 1); button_box.pack_start(button_06, true, true, 1)
button_box.pack_start(button_07, true, true, 1); button_box.pack_start(button_08, true, true, 1); button_box.pack_start(button_09, true, true, 1)
button_box.pack_start(button_10, true, true, 1)

pos_entry_box.pack_start(pos_entry_01, true, true, 2); pos_entry_box.pack_start(pos_entry_02, true, true, 2)
pos_entry_box.pack_start(pos_entry_03, true, true, 2); pos_entry_box.pack_start(pos_entry_04, true, true, 2)
pos_entry_box.pack_start(pos_entry_05, true, true, 2); pos_entry_box.pack_start(pos_entry_06, true, true, 2)
pos_entry_box.pack_start(pos_entry_07, true, true, 2); pos_entry_box.pack_start(pos_entry_08, true, true, 2)
pos_entry_box.pack_start(pos_entry_09, true, true, 2); pos_entry_box.pack_start(pos_entry_10, true, true, 2)

entry_box.pack_start(entry_01, true, true, 2); entry_box.pack_start(entry_02, true, true, 2)
entry_box.pack_start(entry_03, true, true, 2); entry_box.pack_start(entry_04, true, true, 2)
entry_box.pack_start(entry_05, true, true, 2); entry_box.pack_start(entry_06, true, true, 2)
entry_box.pack_start(entry_07, true, true, 2); entry_box.pack_start(entry_08, true, true, 2)
entry_box.pack_start(entry_09, true, true, 2); entry_box.pack_start(entry_10, true, true, 2)

entry_01.text = "[[:digit:]]"; entry_02.text = "[a-z]"; entry_03.text = "[[:digit:]]"; entry_04.text = "\\w"
entry_05.text = "'a','b'"; entry_06.text = "[[:blank:]]"; entry_07.text = "[[:upper:]]"; entry_08.text = "\\s"
entry_09.text = "\\d"; entry_10.text = "\\d"

pos_entry_01.text = "3"; pos_entry_02.text = "2"; pos_entry_03.text = "1"; pos_entry_04.text = ""
pos_entry_05.text = ""; pos_entry_06.text = "2,4"; pos_entry_07.text = ""; pos_entry_08.text = ""
pos_entry_09.text = "5"; pos_entry_10.text = "10"

entry_hbox = Gtk::HBox.new()
entry_label = Gtk::Label.new("REGEXP")
entry_regexp = Gtk::Entry.new()

button_01.signal_connect("clicked") do
   # Start with
   begin
      regul.start_with(pos_entry_01.text, entry_01.text)
      entry_regexp.text = regul.regex.to_s
      button_01.sensitive = false
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

button_02.signal_connect("clicked") do
   # Append
   begin
      regul.append(pos_entry_02.text, entry_02.text)
      entry_regexp.text = regul.regex.to_s
      button_01.sensitive = false
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

button_03.signal_connect("clicked") do
   # End with
   begin
      regul.end_with(pos_entry_03.text, entry_03.text)
      entry_regexp.text = regul.regex.to_s
      button_01.sensitive = false; button_02.sensitive = false; button_03.sensitive = false
      button_04.sensitive = false; button_05.sensitive = false; button_06.sensitive = false
      button_07.sensitive = false; button_08.sensitive = false; button_09.sensitive = false
      button_10.sensitive = false
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

button_04.signal_connect("clicked") do
   # Maybe
   begin
      regul.maybe(entry_04.text)
      entry_regexp.text = regul.regex.to_s
      button_01.sensitive = false
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

button_05.signal_connect("clicked") do
   # One of
   begin
      val = Array.new(0)
      entry_05.text.chomp.split(/,/).map { |x| val << x.to_s }       
      regul.one_of(val)       
      entry_regexp.text = regul.regex.to_s       
      button_01.sensitive = false    
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

button_06.signal_connect("clicked") do
   # Between
   begin
      val = Array.new(0)
      pos_entry_06.text.chomp.split(/,/).map { |x| val << x.to_i }       
      regul.between(val, entry_06.text)       
      entry_regexp.text = regul.regex.to_s       
      button_01.sensitive = false    
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

button_07.signal_connect("clicked") do
   # Zero or more
   begin
      regul.zero_or_more(entry_07.text)
      entry_regexp.text = regul.regex.to_s
      button_01.sensitive = false
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

button_08.signal_connect("clicked") do
   # One or more
   begin
      regul.one_or_more(entry_08.text)
      entry_regexp.text = regul.regex.to_s
      button_01.sensitive = false
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

button_09.signal_connect("clicked") do
   # At least
   begin
      regul.at_least(pos_entry_09.text.to_i, entry_09.text)
      entry_regexp.text = regul.regex.to_s
      button_01.sensitive = false
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

button_10.signal_connect("clicked") do
   # At most
   begin
      regul.at_most(pos_entry_10.text.to_i, entry_10.text)
      entry_regexp.text = regul.regex.to_s
      button_01.sensitive = false
   rescue => e
      message(window, "Problem", e.to_s)
   end
end

gener_container.pack_start(button_box, true, true, 1)
gener_container.pack_start(pos_entry_box, true, true, 1)
gener_container.pack_start(entry_box, true, true, 1)
gener_box.pack_start(gener_container, true, true, 1)

gener_box.pack_start(separator1, false, false, 5)

entry_hbox.pack_start(entry_label, false, false, 1)
entry_hbox.pack_start(entry_regexp, true, true, 1)

gener_box.pack_start(entry_hbox, true, true, 1)

gener_box.pack_start(separator2, false, false, 5)

button_box = Gtk::HBox.new(false, 1)

button_reset = Gtk::Button.new("Reset")
button_exit = Gtk::Button.new("Exit")

button_reset.signal_connect("clicked") do
   entry_regexp.text = ""
   regul = nil
   regul = Regularity.new()
   button_01.sensitive = true; button_02.sensitive = true; button_03.sensitive = true
   button_04.sensitive = true; button_05.sensitive = true; button_06.sensitive = true
   button_07.sensitive = true; button_08.sensitive = true; button_09.sensitive = true
   button_10.sensitive = true
end

button_exit.signal_connect("clicked") do
   Gtk.main_quit
end

button_box.pack_start(button_reset, true, true, 1)
button_box.pack_start(button_exit, true, true, 1)

gener_box.pack_start(button_box, true, true, 1)

# ---------- Widgets to put into inspect_box ------------
inspect_box = Gtk::VBox.new()
inspect_box.border_width = 10

regexp_inspect_hbox = Gtk::HBox.new()
regexp_insp_label = Gtk::Label.new("REGEXP")
regexp_insp_entry = Gtk::Entry.new()
regexp_inspect_hbox.pack_start(regexp_insp_label, false, false, 2)
regexp_inspect_hbox.pack_start(regexp_insp_entry, true, true, 2)

inspect_box.pack_start(regexp_inspect_hbox, true, true, 2)

regexp_ins_outbox = Gtk::VBox.new()

regexp_ins_frame = Gtk::Frame.new(" Results ")
regexp_ins_textview = Gtk::TextView.new()
regexp_ins_textview.set_size_request(-1, 300)
regexp_ins_textview.border_width = 1
regexp_ins_scrolled = Gtk::ScrolledWindow.new()
regexp_ins_scrolled.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
regexp_ins_scrolled.add(regexp_ins_textview)
regexp_ins_frame.add(regexp_ins_scrolled)

inspect_box.pack_start(regexp_ins_frame, true, true, 2)

inspect_ctrl_hbox = Gtk::HBox.new()
inspect_ctrl_ok_button = Gtk::Button.new("Execute")
inspect_ctrl_clr_button = Gtk::Button.new("Clear")
inspect_ctrl_exit_button = Gtk::Button.new("Exit")

inspect_ctrl_hbox.pack_start(inspect_ctrl_ok_button, true, true, 1)
inspect_ctrl_hbox.pack_start(inspect_ctrl_clr_button, true, true, 1)
inspect_ctrl_hbox.pack_start(inspect_ctrl_exit_button, true, true, 1)

inspect_ctrl_ok_button.signal_connect("clicked") do
   Regexp::Scanner.scan /#{regexp_insp_entry.text}/ do |type, token, text, ts, te|
      regexp_ins_textview.buffer.text += "type: #{type}, token: #{token}, text: '#{text}' [#{ts}..#{te}]\n"
   end
end

courier = Pango::FontDescription.new("Courier New 9")
regexp_ins_textview.modify_font(courier)

inspect_ctrl_clr_button.signal_connect("clicked") do
   regexp_ins_textview.buffer.text = ""
end

inspect_ctrl_exit_button.signal_connect("clicked") do
   Gtk.main_quit
end

inspect_box.pack_start(inspect_ctrl_hbox, true, true, 1)

# -------------------------------------------------------

help_box = Gtk::VBox.new()
help_box.border_width = 10

help_frame = Gtk::Frame.new(" Help ")
help_textview = Gtk::TextView.new()
help_textview.set_size_request(-1, 300)
help_textview.border_width = 1
help_scrolled = Gtk::ScrolledWindow.new()
help_scrolled.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
help_scrolled.add(help_textview)
help_frame.add(help_scrolled)

help_box.pack_start(help_frame, true, true, 2)

font = Pango::FontDescription.new("Courier New 9")
help_textview.modify_font(font)

help =<<'HELP'
---------------
Metacharacters:
---------------
/./ - Any character except a newline.
/./m - Any character (the m modifier enables multiline mode)
/\w/ - A word character ([a-zA-Z0-9_])
/\W/ - A non-word character ([^a-zA-Z0-9_])
/\d/ - A digit character ([0-9])
/\D/ - A non-digit character ([^0-9])
/\h/ - A hexdigit character ([0-9a-fA-F])
/\H/ - A non-hexdigit character ([^0-9a-fA-F])
/\s/ - A whitespace character: /[ \t\r\n\f]/
/\S/ - A non-whitespace character: /[^ \t\r\n\f]/
-------------------------------
POSIX character representation:
-------------------------------
/[[:alnum:]]/ - Alphabetic and numeric character
/[[:alpha:]]/ - Alphabetic character
/[[:blank:]]/ - Space or tab
/[[:cntrl:]]/ - Control character
/[[:digit:]]/ - Digit
/[[:graph:]]/ - Non-blank character (excludes spaces, control characters, and similar)
/[[:lower:]]/ - Lowercase alphabetical character
/[[:print:]]/ - Like [:graph:], but includes the space character
/[[:punct:]]/ - Punctuation character
/[[:space:]]/ - Whitespace character ([:blank:], newline, carriage return, etc.)
/[[:upper:]]/ - Uppercase alphabetical
/[[:xdigit:]]/ - Digit allowed in a hexadecimal number (i.e., 0-9a-fA-F)
-------------------------
Character representation:
-------------------------
/\p{Alnum}/ - Alphabetic and numeric character
/\p{Alpha}/ - Alphabetic character
/\p{Blank}/ - Space or tab
/\p{Cntrl}/ - Control character
/\p{Digit}/ - Digit
/\p{Graph}/ - Non-blank character (excludes spaces, control characters, and similar)
/\p{Lower}/ - Lowercase alphabetical character
/\p{Print}/ - Like \p{Graph}, but includes the space character
/\p{Punct}/ - Punctuation character
/\p{Space}/ - Whitespace character ([:blank:], newline, carriage return, etc.)
/\p{Upper}/ - Uppercase alphabetical
/\p{XDigit}/ - Digit allowed in a hexadecimal number (i.e., 0-9a-fA-F)
/\p{Word}/ - A member of one of the following Unicode general category Letter, Mark, Number, Connector_Punctuation
/\p{ASCII}/ - A character in the ASCII character set
/\p{Any}/ - Any Unicode character (including unassigned characters)
/\p{Assigned}/ - An assigned character
Pattern repetitions:
--------------------
* - Zero or more times
+ - One or more times
? - Zero or one times (optional)
{n} - Exactly n times
{n,} - n or more times
{,m} - m or less times
{n,m} - At least n and at most m times
--------
Anchors:
--------
^ - Matches beginning of line
$ - Matches end of line
\A - Matches beginning of string.
\Z - Matches end of string. If string ends with a newline,it matches 
just before newline
\z - Matches end of string
\G - Matches point where last match finished
\b - Matches word boundaries when outside brackets; backspace (0x08) 
when inside brackets
\B - Matches non-word boundaries
(?=pat) - Positive lookahead assertion: ensures that the following characters match pat, but doesn't include those characters in the matched text
(?!pat) - Negative lookahead assertion: ensures that the following characters do not match pat, but doesn't include those characters in the matched text
(?<=pat) - Positive lookbehind assertion: ensures that the preceding characters match pat, but doesn't include those characters in the matched text
(?<!pat) - Negative lookbehind assertion: ensures that the preceding characters do not match pat, but doesn't include those characters in the matched text
--------
Options:
--------
/pat/i - Ignore case
/pat/m - Treat a newline as a character matched by .
/pat/x - Ignore whitespace and comments in the pattern
/pat/o - Perform #{} interpolation only once
HELP

help_textview.buffer.text = help

main_nb.append_page(gener_box, gener_label)
main_nb.append_page(inspect_box, inspect_label)
main_nb.append_page(help_box, help_label)

window.add(main_nb)

window.show_all

Gtk.main

Kod narzędzia wyszukiwawczego:

# coding 'utf-8'
require 'regexp_parser'
require 'optparse'

def banner
   puts "\nREGEXP inspector and processor by Janusz Nawrat (2014)"
   puts "=" * 70
   puts "Usage: ruby #{File::basename($0,)} -r REGEXP -o operation [-f INPUT_FILE]\n\n"
   puts "where:"
   puts "        -r or --regexp should be the REGEXP string to inspect" 
   puts "           or process"
   puts "        -f or --filename - optional parameter - the name of input" 
   puts "           data file. If not set, STDIN will be assumed instead"
   puts "        -o or --operation should be 'inspect' or 'process'"   
   puts "        -h for getting help"
   puts "=" * 70
end

def help()
help =<<'HELP'
---------------
Metacharacters:
---------------
/./ - Any character except a newline.
/./m - Any character (the m modifier enables multiline mode)
/\w/ - A word character ([a-zA-Z0-9_])
/\W/ - A non-word character ([^a-zA-Z0-9_])
/\d/ - A digit character ([0-9])
/\D/ - A non-digit character ([^0-9])
/\h/ - A hexdigit character ([0-9a-fA-F])
/\H/ - A non-hexdigit character ([^0-9a-fA-F])
/\s/ - A whitespace character: /[ \t\r\n\f]/
/\S/ - A non-whitespace character: /[^ \t\r\n\f]/
-------------------------------
POSIX character representation:
-------------------------------
/[[:alnum:]]/ - Alphabetic and numeric character
/[[:alpha:]]/ - Alphabetic character
/[[:blank:]]/ - Space or tab
/[[:cntrl:]]/ - Control character
/[[:digit:]]/ - Digit
/[[:graph:]]/ - Non-blank character (excludes spaces, control 
characters, and similar)
/[[:lower:]]/ - Lowercase alphabetical character
/[[:print:]]/ - Like [:graph:], but includes the space character
/[[:punct:]]/ - Punctuation character
/[[:space:]]/ - Whitespace character ([:blank:], newline, 
carriage return, etc.)
/[[:upper:]]/ - Uppercase alphabetical
/[[:xdigit:]]/ - Digit allowed in a hexadecimal number (i.e., 0-9a-fA-F)
-------------------------
Character representation:
-------------------------
/\p{Alnum}/ - Alphabetic and numeric character
/\p{Alpha}/ - Alphabetic character
/\p{Blank}/ - Space or tab
/\p{Cntrl}/ - Control character
/\p{Digit}/ - Digit
/\p{Graph}/ - Non-blank character (excludes spaces, control 
characters, and similar)
/\p{Lower}/ - Lowercase alphabetical character
/\p{Print}/ - Like \p{Graph}, but includes the space character
/\p{Punct}/ - Punctuation character
/\p{Space}/ - Whitespace character ([:blank:], newline, 
carriage return, etc.)
/\p{Upper}/ - Uppercase alphabetical
/\p{XDigit}/ - Digit allowed in a hexadecimal number (i.e., 0-9a-fA-F)
/\p{Word}/ - A member of one of the following Unicode general category 
Letter, Mark, Number, Connector_Punctuation
/\p{ASCII}/ - A character in the ASCII character set
/\p{Any}/ - Any Unicode character (including unassigned characters)
/\p{Assigned}/ - An assigned character
Pattern repetitions:
--------------------
* - Zero or more times
+ - One or more times
? - Zero or one times (optional)
{n} - Exactly n times
{n,} - n or more times
{,m} - m or less times
{n,m} - At least n and at most m times
--------
Anchors:
--------
^ - Matches beginning of line
$ - Matches end of line
\A - Matches beginning of string.
\Z - Matches end of string. If string ends with a newline,it matches 
just before newline
\z - Matches end of string
\G - Matches point where last match finished
\b - Matches word boundaries when outside brackets; backspace (0x08) 
when inside brackets
\B - Matches non-word boundaries
(?=pat) - Positive lookahead assertion: ensures that the following 
characters match pat, but doesn't include those characters 
in the matched text
(?!pat) - Negative lookahead assertion: ensures that the following 
characters do not match pat, but doesn't include those characters 
in the matched text
(?<=pat) - Positive lookbehind assertion: ensures that the preceding 
characters match pat, but doesn't include those characters 
in the matched text
(?<!pat) - Negative lookbehind assertion: ensures that the preceding  characters do not match pat, but doesn't include those characters  in the matched text 
-------- Options: -------- 
/pat/i - Ignore case 
/pat/m - Treat a newline as a character matched by 
/pat/x - Ignore whitespace and comments in the pattern 
/pat/o - Perform #{} interpolation only once 
HELP    
   puts help 
end 

def inspection(regular)    
   output_text = ""    
   Regexp::Scanner.scan(/#{regular}/) do |type, token, text, ts, te|       
      output_text += "type: #{type}, token: #{token}, text: '#{text}' [#{ts}..#{te}]\n"    
   end    
   return output_text 
end 

def process(position, regular, string)    
   group = Array.new(); group = regular.to_s.scan(/\(.+?\)/)    
   hash = Hash[group.map.with_index.to_a]    # => {"a"=>0, "b"=>1, "c"=>2}
   matched = false
   if string.match(/#{regular}/)
      puts "MATCH (#{position}): #{string}"
      matched = true
      $whole += 1
   else
      group.each do |elem|
         if string.match(/#{elem}/)
            puts "PARTIAL MATCH (#{position})#{elem}: #{string}"
            matched = true   
            $partial += 1
         end   
      end
   end
end

$whole = $partial = 0

options = {:regexp => nil, :filename => nil, :operation => nil}

parser = OptionParser.new do |opts|
   opts.banner = "Usage: #{$0} [options]"
   
   opts.on('-o', '--operation oper', ['inspect', 'process'], 'Operation (inspect/process)') do |oper|
      options[:operation] = oper;
   end

   opts.on('-r', '--regexp reg', 'Regular expresion') do |reg|
      options[:regexp] = reg;
   end

   opts.on('-f', '--filename [file]', 'Input file name') do |filename|
      options[:filename] = filename;
   end
      
   opts.on('-h', '--help', 'Displays Help') do
      banner
      help
      exit
   end
end

parser.parse!

if options[:operation] == nil
   banner
   unless (options[:operation] == 'inspect') or (options[:operation] == 'process')
      STDERR.puts "\nUnknown operation type: must be inspect or process"
      exit
   end
end

if options[:regexp] == nil
   banner
   exit
end

if options[:filename] == nil
   options[:file] = 'ARGF'
elsif not File::exist?(options[:filename])
   STDERR.puts "Error in opening file: #{options[:filename]}"
   exit
end

input_file = options[:filename]
regexp = options[:regexp]

case options[:operation]
   when "inspect"
      puts "Regular expression: #{regexp} inspection:"
      puts inspection(regexp)
   when "process"
      counter = 1
      if input_file != nil
         File.open(input_file, "r") do |infile|
            while (line = infile.gets)
               # puts "#{counter}: #{line}"
               process(counter, regexp, line)
               counter = counter + 1
            end
         end
      else
         ARGF.each do |line|
            process(counter, regexp, line)
            counter = counter + 1
         end
      end
      puts '_' * 60 + "\nNumber of full matches: #{$whole}, partial matches: #{$partial}"
   else 
      puts "No such operation #{options[:operation]}. Must be process or inspect"
end

Informacje Janusz Nawrat
Just ordinary man who likes thinking...

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

TOMASZ WEŁNA

artysta grafik | wykładowca

PRACOWNIA OKO

Szkoła Rysunku Malarstwa i Grafiki DR TOMASZA WEŁNY | KRAKÓW | Plac Matejki 10 | tel 691 81 75 74

Piękno neurobiologii

Blog Jerzego Vetulaniego

Teoria muzyki, zasady muzyki, podstawy muzyki

Teoria muzyki, zasady muzyki, podstawy muzyki - czyli to co każdy amator muzyki wiedzieć powinien :)

Personal Development & Inspirations

Przemyślenia i refleksje, którymi warto się podzielić (blog by Janusz Nawrat)

Business IT Cooperation Platform

Biznes i IT - dwa światy, które muszą współdziałać

%d bloggers like this: