Tips in Action‎ > ‎정규식‎ > ‎

정규식에서 일치하는 내용들을 변수로 추출하기

전종필

정규식은 어떤 텍스트가 일정 패턴에 일치하는지 검사하거나, 특정 패턴을 다른 텍스트로 치환하기 위해 사용하는 것이 보통입니다.
그러나, 패턴을 이용하면 원하는 내용을 텍스트로부터 추출해 낼 수 있습니다.
예를 들어서 HTML 문서에서 <TITLE> 태그의 내용만을 추출한다면 다음과 같이 할 수 있습니다.

$html=<<EOT;
<html><head>
<title>제목입니다</title>
</head><body><h1>Useless body</h1></body></html>
EOT

if( $html =~ m{<title>(.*)</title>}i ) { # m{}에 대해선 관련 팁 참조
	$title = $1;
}
정규식(m//이건 s///이건)을 사용하여, 일치되었을 때에는 괄호()안의 정규식에 해당하는 부분은 앞에서부터 순서대로 $1, $2, $3 ... 등의 순의 특수 변수에 저장이 됩니다.
따라서 if절에서 <title>(.*)</title> 패턴이 참이 되므로, $1에 태그 내부의 문자열이 저장되고, 이를 $title 변수로 다시 뽑아내었습니다.

그러나, 다음과 같은 경우에는

# 211.38.34.215 - - [18/Apr/2002:23:22:36 -0900] "GET /mod/svrtest HTTP/1.1" 200 8413
if( $logline =~ m!^([\.\d]+).+\[(\d+)/(\w+)/(\d+):(\d+):(\d+):(\d+) [-+]\d+\] "(GET|POST) (\S+) \S+ (\d+)! ) {
	my( $rem_addr, $date, $mstr, $year, $hh, $mm, $ss, $method, $uri, $response_code )
	= ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 );
	# print "$rem_addr, [$year-$mstr-$date, $hh:$mm:$ss], $method $uri .. $response_code\n";
}
좀 불편하다는 느낌입니다.
일단, if 자체가 불필요할만큼 정확한 형식의 문자열(아파치 로그파일)이고, $1, $2, $3 어쩌고 쓰기도 귀찮습니다.
이 때는 다음과 같이 사용하면 편리합니다.

my( $rem_addr, $date, $mstr, $year, $hh, $mm, $ss, $method, $uri, $response_code )
  = ( $logline =~ m!^([\.\d]+).+\[(\d+)/(\w+)/(\d+):(\d+):(\d+):(\d+) [-+]\d+\] "(GET|POST) (\S+) \S+ (\d+)! );
= 뒷쪽의 괄호 안에 정규식 일치식이 참이면, = 앞의 목록, 혹은 배열에는 정규식 내부의 괄호 안에 해당되는 내용들이 순서대로 들어갑니다.
이와 같은 문자열의 추출은 많이 사용되므로 꼭 기억해 두시기 바랍니다.
Comments