본문으로 바로가기

문자열 가공 및 원하는 자료 추출

category 카테고리 없음 2025. 3. 24. 10:18

bonkora 님이 작성해 주신 강좌입니다.

 

이 글에서 정규표현식 자체에 대해 쓰려는 것은 아니고 Tcl/Tk에서 정규표현식을 사용하여 자료로부터 원하는 부분을 추출하는 방법을 간단하게 알아보려 합니다. 물론 실제론 여기에 나온 예보다 훨씬 더 복잡한 경우도 많겠으나 일단 기본적인 방법만 기술합니다.

준비운동: 특정 문자열에 특정 패턴이 포함되어 있는지 알아보는 방법

예를 들어 "regular expression"이라는 문자열에 "pre"라는 패턴이 들어 있는지 알아보려면,

regexp {reg} {regular expression}

이렇게 하면 됩니다. 들어 있으면 1을, 들어 있지 않으면 0을 돌려줍니다. 그래서 특정 문자열에 특정 패턴이 들어 있을 때만 어떤 코드를 실행시키고 싶으면 이렇게 하면 되겠지요.

if {[regexp {reg} {regular expression}]} {
    puts "포함되어 있음."
}

대소문자를 구별하지 않으려면 -nocase 플랙을 사용하여 이렇게 합니다.

regexp -nocase {Reg} {regular expression}

고정된 위치에 있는 문자열을 추출하고 싶을 때

예를 들어 아래에 보이는 자료에 나오는 숫자들같이 고정된 위치에 있는 자료를 추출하는 경우를 생각해 봅시다.

Kim  : 180
Park :  44
Chang: 140

숫자가 8번째부터 10번째 칼럼에 위치하므로 string 명령을 사용하여 그 범위의 글자를 뽑으면 됩니다. string 명령에서는 맨 앞의 글자의 인덱스가 0인 것에 주의하십시오. 즉, 첫 번째 글자가 string 명령에선 0번째 글자가 된다고 생각하면 됩니다.

string range "Kim  : 180" 7 9

이렇게 하면 원하는 부분의 문자열을 돌려줍니다. 그 문자열을 변수에 저장했다가 사용하려면 이렇게 합니다.

set str [string range "Kim  : 180" 7 9]
puts $str

고정된 위치는 없이 일정한 패턴으로 이어지는 문자열

예를 들어 이런 자료가 있다고 합시다.

January Kang 12345 Pusan
February Chung 54968 Daegu
March Lee 74565 Seoul

한 레코드(즉 한 줄의 자료) 내에서는 공백에 의해 각 필드(한 줄에 포함되어 있는 각각의 자료)가 분리되는 형태입니다. 한 줄이 하나의 리스트로 취급합니다. 예를 들어 위의 자료에서 세 번째 칼럼의 숫자 부분을 추출한다고 합니다. 리스트에서 가장 앞의 문자열의 인덱스는 0입니다.

set str "January Kang 12345 Pusan"
puts [lindex $str 2]

이렇게 하면 됩니다. 그런데 자료가 이렇게 콤마로 구분되어 있는 경우도 있겠지요.

January, Kang, 12345, Pusan
February, Chung, 54968, Daegu
March, Lee, 74565, Seoul

리스트로 다루어서 필요한 자료를 추출하여 콤마를 없애버리는 방법도 쓸 수 있을 것이지만 regexp 명령으로 숫자로만 이루어진 부분을 뽑아내는 방법을 써 보겠습니다.

set str "January, Kang, 12345, Pusan"
regexp {[0-9]+} $str match
puts $match

이렇게 변수 str에서 패턴 "{[0-9]+}"에 해당하는 문자열을 찾아서 변수 match에 저장합니다. "[0-9]"는 0부터 9까지의 숫자 한 개를 의미하고 "+"는 그 숫자가 한 개 이상 연속됨을 의미합니다. 그런데 숫자 부분뿐 아니라 한 줄(레코드)의 네 항목(필드)을 모두 추출하려면 어떻게 할까요. 콤마와 공백 한 개가 있는 부분을 잘라서 네 개의 변수에 저장하는 방법을 사용하면 되겠습니다. 역시 regexp 명령을 사용합니다.

set str "January, Kang, 12345, Pusan"
regexp {(.*), (.*), (.*), (.*)} $str match s1 s2 s3 s4
puts $match
puts $s1
puts $s2
puts $s3
puts $s4

이렇게 해 보면 패턴 "{(.*), (.*), (.*), (.*)}"에 매치되는 문자들은 변수 match에 저장되고 서브패턴 즉 "(.*)" 4개에 해당하는 자료들은 변수 s1, s2, s3, s4에 저장됩니다. "("와 ")"안에 있는 내용이 서브패턴입니다. 만약 패턴 내에 서브패턴이 없다면 변수 s1, s2, s3, s4는 필요가 없습니다. 이런 방법 말고 위 자료들을 좀 다루기 쉬운 리스트로 만들어 버리는 방법도 괜찮을 것 같습니다. regsub 명령을 사용합니다.

set str "January, Kang, 12345, Pusan"
regsub -all {, } $str { } str2
puts $str2

위에서 regsub 명령은 변수 str 내에서 ", "을 찾아서 " "로 바꿉니다. 이렇게 하면 str2는 "January Kang 12345 Pusan"의 값을 가지게 되어 좀 더 다루기가 쉬워질 것으로 생각됩니다. 스위치 "-all"이 없으면 치환작업을 한 번만 하므로 이 스위치를 꼭 넣어야 합니다. 

마무리운동: 특정 문자열에 특정 패턴이 몇 번 나오는지 알아보기

예를 들어 특정한 문장에서 'te' 또는 'he' 'ge'가 몇 번 나오는지 세어보기로 합니다. 이 경우에는 regsub 명령을 사용합니다. regsub 명령이 치환 후에 치환이 일어난 횟수를 돌려주기 때문입니다.

set str "This software is copyrighted by the Regents of the University of California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
Corporation and other parties."
set howmany [regsub -all {[thg]e} $str {} ignore]
puts "$howmany 번 치환되었습니다."

"[thg]"는 "t"와 "h"와 "g" 중의 한 글자를 의미합니다. 변환된 문자열은 변수 ignore에 저장됩니다만, 이 경우엔 치환된 횟수를 구하는 것이기 때문에 큰 의미는 없겠습니다. "-all" 스위치를 넣는 것을 잊어버리면 안 되겠죠?