Wyszukiwanie dopasowań do wyrażeń regularnych w plikach tekstowych oraz podmiana łańcuchów znakowych w miejscach dopasowań

Posługiwanie się wyrażeniami regularnymi do wyszukiwania dopasowań do wzorców lub wykonywania podmian łańcuchów znakowych w miejscu ich występowania w plikach tekstowych, na przykład w plikach konfiguracyjnych albo w logach systemowych czy aplikacyjnych, należy do kanonu codziennych, podstawowych czynności każdego administratora systemów czy analityka bezpieczeństwa. W takich sytuacjach z pomocą mogą przyjść własne narzędzia skryptowe.

1355638821_regexp_2W tym miejscu ktoś mógłby zapytać: „…ale po co pisać skrypty, skoro mamy gotowe narzędzia, takie jak choćby grep, sed czy awk?”. To prawda, że jeśli akurat mamy dostęp do tego rodzaju narzędzi, to śmiało możemy, a nawet powinniśmy z nich korzystać. Natomiast skrypty przydadzą się z pewnością, gdy zajdzie potrzeba wzbogacenia podstawowej funkcjonalności oferowanej przez te narzędzia, o coś więcej – w najprostszym przypadku choćby o wizualizację dopasowań i substytucji, co też czyni skrypt stanowiący część artykułu. W nieco bardziej skomplikowanych przypadkach możemy potrzebować – załóżmy – anonimizować wyselekcjonowane dane, przeprowadzać ich szyfrowanie, czy też wykonywać na nich inne operacje kryptograficzne, dokonywać operacji arytmetycznych czy statystycznych, przygotowywać raporty, wykresy, dashboardy itp. Wszędzie tam, zwłaszcza gdy trzeba działać szybko i skutecznie, skrypty nie mają żadnej konkurencji.

Wracając na chwilę do podstawowych narzędzi systemowych, pomijając może grep-a z racji jego popularności i powszechnej znajomości, dopasowania do wyrażeń regularnych możemy znajdować w następujący sposób:

sed -n '/search_for_pattern/p' <input_file

albo:

sed -n 's/search_for_pattern/&/p' <input_file

W tym drugim przypadku mamy do czynienia z mogącą uchodzić za nieco dziwną praktyką substytucji wzorca na reprezentujący go identyczny łańcuch znaków, a więc swego rodzaju substytucji bez substytucji. Jednak to działa, pokazując jednocześnie, że niemal każdy problem na ogół ma wiele skutecznych rozwiązań.

Narzędziem awk szukamy dopasowań do wyrażenia regularnego w następujący sposób:

awk '/search_for_pattern/ { print }' <input_file

albo, jeśli chcielibyśmy wyprowadzić wyłącznie dopasowania, zamiast pełnych wierszy tekstu, w których one wystąpiły, to moglibyśmy zrobić to tak:

awk 'match($0, /search_for_pattern/) {
    print substr($0, RSTART, RLENGTH);
}' <input_file

Podstawienie łańcucha znaków w miejscu wystąpienia dopasowania jest operacją prostą w narzędziu sed:

sed 's/search_for_pattern /replace_to_string/g' <input_file >output_file

Przy pomocy awk można to zrobić tak:

awk ‘gsub(/search_for_pattern/, "repl_str"); { print }’ <input_file >output_file

Skrypt dołączony do artykułu został wyposażony w graficzny interfejs użytkownika napisany w oparciu widgety biblioteki Tk. Z poziomu menu skryptu możemy załadować do programu plik tekstowy, a potem zdecydować o wyborze opcji w menu Operations czy chcemy jedynie wyszukać określonych wyrażeń regularnych czy też dodatkowo przeprowadzić ich substytucję. Wyniki podstawienia możemy zapisać korzystając z opcji File –> Save.

Graficzny interfejs użytkownika przedstawia się następująco:

regexp

Opcja Help programu pozwala na uzyskanie podpowiedzi odnośnie składni wyrażeń regularnych, co – jestem przekonany – zawsze może się przydać.

regexp_help

Skrypt co prawda liczy wiele wierszy kodu źródłowego, ale jego podstawową funkcjonalność w zakresie wyszukiwania w standardowym strumieniu wejściowym dopasowań do wyrażenia regularnego można zrealizować niewielkiego rozmiaru kodem:

if {$argc < 1} {
   puts stderr "Regexp needed: $argv0 regexp"
   exit
}

set pattern [lindex $argv 0]

while { ![eof stdin] } {
   set string [gets stdin]
    if {[regexp $pattern $string match]} {
      puts $string
   }
}

Jeśli zaś chodzi o kod realizujący substytucję wyrażeń regularnych, to jest on równie krótki i prosty:

if {$argc < 2} {
   puts stderr "Regexp needed: $argv0 match subst"
   exit
}

set pattern [lindex $argv 0]
set subst [lindex $argv 1]

while { ![eof stdin] } {
   set string [gets stdin]
    if {[regexp $pattern $string match]} {
      regsub -all "$pattern" $string "$subst" string
      puts $string
   }
}

Oto pełny kod skryptu:

proc open_file {widget} {
   set types {
      {"All txt files"     {.txt} }
      {"Other files"             *}
   }
   
   set filename [tk_getOpenFile -filetypes $types -parent .]
   
   if { [catch {open $filename r} f] } {
      tk_messageBox -message "Problem with opening input file for reading" -title {Problem}
      return
   }
   
   while {![eof $f]} {
      $widget insert end [gets $f]
      $widget insert end "\n"
   }
   close $f
}

proc save_to_file {widget} {
   set types {
      {"All txt files"     {.txt} }
      {"Other files"             *}
   }
   
   set filename [tk_getSaveFile -filetypes $types -parent .]
   
   if { [catch {open $filename w} f] } {
      tk_messageBox -message "Problem with opening output file for writing" -title {Problem}
      return
   }
   puts $f [$widget get 1.0 end]
   close $f
}

proc regexp_match {txt_widget} {
   global search
   set content [$txt_widget get 1.0 end]
   $txt_widget delete 1.0 end
   foreach {line} [ split $content "\n" ] {
      if {[regexp "$search" $line]} {
         $txt_widget insert end "$line\n" distinguish
      } else {
         $txt_widget insert end "$line\n"
      }
   }
}

proc regexp_subst {txt_widget} {
   global search subst
   set content [$txt_widget get 1.0 end]
   $txt_widget delete 1.0 end
   foreach {line} [ split $content "\n" ] {
      if {[regexp "$search" $line]} {
         regsub -all "$search" $line "$subst" line
         $txt_widget insert end "$line\n" distinguish
      } else {
         $txt_widget insert end "$line\n"
      }
   }
}

set help {
---------------
Metacharacters:
---------------
. - Any character except a newline.
\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)
--------------------
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
}

proc show_help_window {w} {
   catch {destroy $w}
   toplevel $w
   wm title $w "REGEXP help"
   wm resizable $w 0 0
   global help
   labelframe $w.txt_frame -text " REGEXP help "
   text $w.txt_frame.txt -yscrollcommand "$w.txt_frame.scroll_y set" -xscrollcommand "$w.txt_frame.scroll_x set" -wrap none
   scrollbar $w.txt_frame.scroll_y -command "$w.txt_frame.txt yview" -orient v
   scrollbar $w.txt_frame.scroll_x -command "$w.txt_frame.txt xview" -orient h
   $w.txt_frame.txt insert end $help
   $w.txt_frame.txt configure -state disabled
   button $w.close_button -text Close -command "destroy $w"
   pack $w.txt_frame
   pack $w.txt_frame.scroll_y -fill y -side right -expand 1
   pack $w.txt_frame.scroll_x -fill x -side bottom -expand 1
   pack $w.txt_frame.txt -side left -expand 1 -fill both
   pack $w.close_button
}

menu .mbar
. configure -menu .mbar

menu .mbar.fl -tearoff 1
.mbar add cascade -menu .mbar.fl -label File -underline 0

.mbar.fl add command -label Open -command { pack forget .f.pane.two; .f.pane forget .f.pane.two; open_file .f.pane.one.txt }
.mbar.fl add command -label Save -command { pack forget .f.pane.two; .f.pane forget .f.pane.two; save_to_file .f.pane.one.txt }
.mbar.fl add separator
.mbar.fl add command -label Exit -command { exit }

menu .mbar.op -tearoff 1
.mbar add cascade -menu .mbar.op -label Operations -underline 0

set replace 0

.mbar.op add command -label "RegExp Find" -command { pack .f.pane.two; .f.pane add .f.pane.two; set replace 0;
   .f.pane.two.replace_entry configure -state disabled
 }
 
.mbar.op add command -label "RegExp Substitute" -command { pack .f.pane.two; .f.pane add .f.pane.two; set replace 1; 
   .f.pane.two.replace_entry configure -state normal 
}

menu .mbar.help -tearoff 1
.mbar add cascade -menu .mbar.help -label Help -underline 0
.mbar.help add command -label "REGEXP help" -command { show_help_window .window }


frame .f
panedwindow .f.pane -orient vertical

labelframe .f.pane.one -height 50 -width 50 -text "Input/Output"
text .f.pane.one.txt -yscrollcommand ".srl_y set" -xscrollcommand ".srl_x set" -wrap none
scrollbar .srl_y -command ".f.pane.one.txt yview" -orient v
scrollbar .srl_x -command ".f.pane.one.txt xview" -orient h

.f.pane.one.txt tag configure distinguish -foreground red -relief solid -borderwidth 1

pack .srl_y -in .f.pane.one -fill y -side right -expand 1
pack .srl_x -in .f.pane.one -fill x -side bottom -expand 1
pack .f.pane.one.txt -side left -expand 1 -fill both

labelframe .f.pane.two -height 50 -width 50 -text "Parameters"
label .f.pane.two.search_label -text "Search for (regexp)"
label .f.pane.two.replace_label -text "Replacement string "

set search "^.*$"
set subst ""

entry .f.pane.two.search_entry -textvariable search
entry .f.pane.two.replace_entry -textvariable subst

button .f.pane.two.execute_button -text Execute -command { 
   switch $replace {
      0 {regexp_match .f.pane.one.txt}
      1 {regexp_subst .f.pane.one.txt}
      default {regexp_match .f.pane.one.txt}
    }
}
pack .f.pane.two.search_label .f.pane.two.search_entry .f.pane.two.replace_label .f.pane.two.replace_entry .f.pane.two.execute_button \
     -side left -expand 1 -fill both -pady 5 -padx 5

.f.pane add .f.pane.one

bind .  { show_help_window .window }

pack .f.pane -expand 1 -fill both
pack .f -expand 1 -fill both

wm title . "RegExp Find & Substitute"
wm resizable . 0 0

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: