지금까지는 정규식 패턴 자체를 어떻게 짜는지를 배웠어요. 오늘은 조금 다른 이야기예요. 패턴은 그대로 두고 정규식의 동작 방식을 바꾸는 스위치, 플래그(옵션)를 배워요. 같은 패턴이라도 플래그를 어떻게 켜느냐에 따라 결과가 확 달라져요. 특히 대소문자를 무시하거나, 여러 줄을 각 줄마다 다루거나, 점이 줄바꿈까지 먹게 하는 세 가지 i, m, s는 실무에서 정말 자주 써요. 하나씩 예제로 만나볼게요. 완전 처음이어도 "아, 스위치 켜는 거구나" 하고 편하게 따라오시면 돼요.

16.1 플래그가 뭐예요?

플래그는 정규식 엔진에게 "이번엔 이런 방식으로 매치해줘"라고 알려주는 옵션이에요. 패턴 안에 넣는 기호가 아니라, 매치를 실행할 때 따로 켜는 스위치라는 게 포인트예요. 파이썬에서는 함수 인자로 re.I, re.M, re.S 같은 걸 넘겨서 켜요. 각각 대소문자 무시, 여러 줄, 점이 줄바꿈까지 매치하는 옵션이에요.


이름이 두 가지씩 있는데 헷갈리지 마세요. re.Ire.IGNORECASE의 짧은 이름이고, re.Mre.MULTILINE, re.Sre.DOTALL의 짧은 이름이에요. 긴 이름이 뜻을 그대로 담고 있으니, 코드 읽을 때 긴 이름을 보면 무슨 옵션인지 바로 알 수 있어요. 이제 하나씩 켜볼게요.


한 가지 미리 말해둘게요. 플래그는 여러 개를 동시에 켤 수도 있어요. 파이썬에서는 세로줄 기호로 re.I | re.M처럼 이어 붙이면 "대소문자도 무시하고 여러 줄로도 다뤄줘"가 돼요. 필요한 스위치를 원하는 만큼 겹쳐 켜는 거죠. 지금은 하나씩 익히는 게 먼저니까, 세 가지를 따로따로 만나본 다음에 합치는 법을 이야기할게요.

16.2 대소문자를 무시하려면? (i)

정규식은 기본적으로 대소문자를 구별해요. hellohello만 잡지 HelloHELLO는 안 잡아요. 그런데 철자만 같으면 대소문자 상관없이 다 잡고 싶을 때가 많죠. 그럴 때 i 플래그를 켜요. 상황은 hello를 대소문자 무시하고 전부 찾는 거예요.


import re


re.findall(r'hello', 'Hello HELLO hello', re.I)


결과는 이래요.


['Hello', 'HELLO', 'hello']


세 가지가 다 잡혔죠? 패턴은 소문자 hello 그대로인데 re.I 하나로 대소문자 구별을 꺼버린 거예요. 이름·아이디·검색어처럼 사용자가 대소문자를 제멋대로 섞어 넣는 값을 다룰 때 이 옵션이 없으면 정말 곤란해요. 저도 검색 기능 만들 때 이거 안 켰다가 "왜 대문자로 치면 안 나오냐"는 문의를 받고 부랴부랴 고친 적이 있어요. 반대로 대소문자를 일부러 구별해야 하는 경우, 예를 들어 프로그래밍 언어의 예약어나 코드처럼 대소문자가 의미를 가지는 값이라면 이 옵션을 켜면 안 돼요. 켜야 할 때와 끄고 둘 때를 상황에 맞게 판단하는 게 중요해요.

16.3 각 줄마다 시작과 끝을 보려면? (m)

여러 줄로 된 텍스트에서 각 줄의 첫 단어를 잡고 싶다고 해볼게요. 줄 시작을 뜻하는 ^를 쓰면 될 것 같은데, 여기 함정이 있어요. 기본 상태에서 ^문자열 전체의 맨 앞 한 곳만 가리키거든요. 그래서 첫 줄만 잡혀요. 각 줄마다 적용되게 하려면 m 플래그를 켜야 해요. 상황을 볼게요. 대상은 abcdef가 줄바꿈으로 나뉜 abc\ndef예요. 여기서 \n은 줄바꿈 한 개를 뜻해요.


re.findall(r'^\w+', 'abc\ndef', re.M)


결과는 이래요.


['abc', 'def']


두 줄의 첫 단어가 모두 잡혔어요. re.M을 켜니 ^가 "문자열의 시작"이 아니라 "각 줄의 시작"으로 바뀐 거예요. 짝을 이루는 $(줄 끝)도 마찬가지로 각 줄의 끝이 돼요. 로그 파일처럼 줄 단위로 뭔가를 뽑아야 할 때 m 플래그는 거의 필수예요. 만약 re.M을 빼고 실행했다면 첫 줄의 abc만 나왔을 거예요. 이 차이를 꼭 기억해두세요.

16.4 점이 줄바꿈까지 먹게 하려면? (s)

.은 "아무 글자나 한 개"를 뜻하죠. 그런데 기본 상태에서는 줄바꿈만은 예외로 안 먹어요. 이게 초보를 은근히 괴롭히는 함정이에요. 예를 들어 ab 사이에 줄바꿈이 있으면 a.b로는 안 잡혀요. 줄바꿈까지 포함해서 잡고 싶으면 s 플래그를 켜요. 상황을 볼게요. 대상은 ab 사이에 줄바꿈이 있는 a\nb예요.


re.findall(r'a.b', 'a\nb', re.S)


결과는 이래요.


['a\nb']


기본 상태였다면 못 잡았을 것을, re.S 덕분에 점이 줄바꿈까지 한 글자로 먹어서 잡아냈어요. sDOTALL, 즉 "점이 모든 것을(줄바꿈 포함) 매치한다"는 뜻이라고 기억하면 쉬워요. 여러 줄에 걸친 덩어리를 통째로 잡아야 할 때, 예를 들어 태그 사이에 줄바꿈이 낀 내용을 잡을 때 이 옵션이 필요해요.


ms는 둘 다 줄바꿈과 관련이 있어서 초보가 자주 헷갈려요. 확실히 구분해둘게요. m앵커(^$)가 줄마다 걸리게 하는 옵션이고, s(.)이 줄바꿈까지 먹게 하는 옵션이에요. 하나는 "위치를 보는 기호", 하나는 "글자를 먹는 기호"에 대한 스위치라 서로 하는 일이 완전히 달라요. 이름으로 외우면 편해요. m은 여러 줄(멀티라인)의 시작·끝, s는 점이 전부(닷올) 먹기, 이렇게요.

16.5 패턴 안에 옵션을 넣을 수도 있나요?

있어요. 함수에 인자를 넘기는 대신, 패턴 맨 앞(?i)처럼 써넣는 방법이에요. 이걸 인라인 플래그(패턴 안에 넣는 옵션)라고 해요. 상황은 cat을 대소문자 무시하고 찾는 거예요.


re.findall(r'(?i)cat', 'Cat CAT cat')


결과는 이래요.


['Cat', 'CAT', 'cat']


인자로 re.I를 넘기지 않았는데도 세 가지가 다 잡혔죠? 패턴 앞의 (?i)re.I와 똑같은 효과를 낸 거예요. 마찬가지로 (?m), (?s)도 있고, (?im)처럼 여러 개를 붙여 쓸 수도 있어요. 패턴 문자열만 보면 옵션까지 한눈에 보인다는 장점이 있어요. 다만 최신 파이썬에서는 이 인라인 플래그를 반드시 패턴 맨 앞에 둬야 해요. 중간에 넣으면 경고가 나거나 에러가 나니, "옵션은 문장 맨 앞에" 하고 습관을 들이면 좋아요.


여기서 다른 언어 이야기도 잠깐 할게요. 파이썬에서는 "전체 다 찾기"를 findall이나 finditer 같은 함수가 맡아요. 그런데 자바스크립트에서는 이걸 g(global, 전역) 플래그로 켜요. 그래서 자바스크립트 코드에서 g 플래그를 보면 "아, 전부 찾겠다는 뜻이구나" 하고 읽으면 돼요. 언어마다 플래그 이름과 켜는 방식이 조금씩 다르지만, "동작을 바꾸는 스위치"라는 개념은 똑같아요.

16.6 오늘 배운 것 정리

[정규식 16] 동작을 바꾸는 플래그 i m s

플래그는 패턴이 아니라 정규식의 동작 방식을 바꾸는 스위치예요. i(IGNORECASE)는 대소문자 구별을 끄고, m(MULTILINE)은 ^$를 각 줄에 적용하고, s(DOTALL)는 점이 줄바꿈까지 먹게 해요. 파이썬에서는 re.I, re.M, re.S를 인자로 넘기거나, 패턴 앞에 (?i), (?m), (?s)를 붙여도 돼요. 여러 개를 re.I | re.M처럼 함께 켜는 것도 가능하고요. 자바스크립트의 g처럼 언어마다 다른 플래그도 있으니, 낯선 코드를 볼 땐 어떤 스위치가 켜져 있는지 먼저 살피는 습관을 들이면 좋아요. 같은 패턴도 스위치 하나로 완전히 다르게 움직인다는 걸 기억해두세요.