개발 · 자동화핵심 · 15분 읽기

정규식으로 로그 찾기와 입력 검증

정규식은 문자열 패턴을 찾고 추출하는 도구입니다. 부분 검색과 전체 검증을 구분하고 입력의 의미는 별도로 확인합니다.

개념부터 찾아보기

정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.

이 노트에서 설명할 수 있게 되는 것

  1. 문자 패턴·일치 범위·캡처 결과·의미 검증을 단계별로 구분한다.
  2. fullmatch·search·match가 다른 결과를 내는 이유를 설명한다.
  3. 잘못된 입력과 긴 실패 입력에서 정확성과 비용을 검토한다.

01패턴의 위치·문자 집합·반복을 따로 해석하기

  • [0-9]는 숫자 한 글자이고 [0-9]+는 하나 이상 이어진 숫자다.
  • .은 기본적으로 임의의 문자 한 개를 의미하지만 줄바꿈 포함 여부는 엔진과 옵션에 달려 있다.
    • 실제 점을 찾으려면 \.처럼 이스케이프한다.
  • ^와 $는 위치 조건이며 안의 [^0-9]는 숫자가 아닌 문자라는 다른 의미다.
    • 같은 기호도 문맥에 따라 역할이 달라진다.
  • *는 바로 앞 패턴의 0회 이상, +는 1회 이상, ?는 0회 또는 1회, {m,n}은 반복 수의 범위다.
  • 전체가 파일 와일드카드와 같지는 않다.
    • 예를 들어 셸의 *.txt와 .*\.txt는 처리하는 언어와 뜻이 다르다.
  • 문자열을 코드 안에 적을 때는 문자열 해석과 해석이라는 두 단계가 생기므로 raw string 표기가 편리하다.
import re
pattern = re.compile(r"[0-9]+\.[0-9]+")
print(pattern.findall("load=1.25 idle=98.75"))
# ["1.25", "98.75"]

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

02search·match·fullmatch의 범위 차이

  • re.는 문자열 안에서 일치가 시작될 수 있는 위치를 탐색한다.
  • re.match는 문자열 시작에서 시도하고 re.fullmatch는 전체 문자열이 패턴에 맞아야 한다.
    • 따라서 (r"[0-9]+", "port=22")는 22를 찾지만 fullmatch는 전체가 숫자가 아니어서 실패한다.
  • 의 탐색 범위는 · 중 어떤 함수를 사용하는지에 따라 달라진다.
  • 일치 객체는 group으로 실제 문자열, start/end/으로 범위를 제공한다.
  • 여러 필드를 괄호로 묶으면 그룹을 추출할 수 있고 (?P<name>...)은 이름을 붙인다.
  • 선택 그룹이 참여하지 않으면 None일 수 있으므로 성공 여부와 각 그룹의 존재를 구분한다.
  • findall은 그룹 유무에 따라 반환 모양이 달라질 수 있어 반복 매치 객체가 필요하면 finditer가 적합하다.
import re
m = re.fullmatch(r"(?P<key>[A-Z_]+)=(?P<value>[0-9]+)", "PORT=8080")
if m:
    print(m.group("key"), int(m.group("value")))
# PORT 8080

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

03탐욕성·최소 일치·플래그와 과도한 매칭

  • 기본 는 가능한 많은 문자를 포함하려는 탐욕적 형태다.
  • .*가 여러 구분자를 지나 마지막까지 잡는 경우 .*?처럼 최소 일치 형태로 좁힐 수 있다.
    • 그러나 최소 일치만 붙이면 모든 파서 문제가 해결되는 것은 아니다.
  • 필드 경계가 분명하다면 [^,]*처럼 구분자를 제외하는 패턴이 의도를 더 직접 표현할 수 있다.
  • IGNORECASE는 대소문자 비교, MULTILINE은 ^/$의 줄 경계, DOTALL은 점의 줄바꿈 일치와 관계된다.
    • 서로 다른 옵션이므로 한꺼번에 전체 줄 처리로 뭉뚱그리지 않는다.
  • 복잡한 중첩 반복과 긴 불일치 입력은 일부 backtracking 엔진에서 많은 계산을 유발할 수 있어 외부 입력 길이와 패턴 구조를 제한한다.
  • HTML·JSON·CSV처럼 구조가 있는 자료는 전용 파서를 사용할 때 인용·중첩·이스케이프 의미를 보존하기 쉽다.
원리 심화

04일치 결과는 범위·그룹·문자 집합으로 읽는다

  • Match.()의 시작과 끝은 문자열 인덱스이며 끝 위치는 포함하지 않는다. 길이는 end-start로 계산하지만 바이트 길이와 같다고 가정하지 않는다.
  • 그룹 0은 전체 일치이고 괄호로 만든 그룹은 하위 필드다. 그룹이 일치에 참여하지 않은 None과 참여했지만 길이가 0인 문자열은 다른 결과다.
  • str 패턴의 \d는 ASCII 0~9 외의 십진 숫자도 포함한다. 프로토콜이 ASCII 숫자만 허용한다면 [0-9] 또는 적절한 re.ASCII 범위를 선택한다.
  • r"\b"는 에 역슬래시와 b를 전달해 단어 경계를 표현한다. 보통 문자열 "\b"는 단계에서 backspace로 해석되므로 같은 표면 문자처럼 보여도 패턴 입력이 달라진다.
  • re.sub의 대체 문자열도 그룹 참조 같은 별도 규칙을 가진다. 패턴을 안전하게 인용했다고 대체값까지 같은 규칙으로 처리됐다고 가정하지 않는다.
예제로 추적

05검색 범위와 선택 그룹을 인덱스로 추적하기

  • 입력 "x=12-AB;"에서 숫자 필드는 인덱스 2부터 3, 영문 필드는 5부터 6이다. 가운데 -는 전체 일치에 포함되지만 두 그룹에는 포함되지 않는다.
  • 의 전체 결과 은 (2, 7)이므로 길이는 5이다. text[2:7]을 읽으면 12-AB가 되어 실제 소비 범위를 대조할 수 있다.
  • 첫 그룹 은 (2, 4), 둘째는 (5, 7)이다. group 결과는 문자열 "12"와 "AB"이므로 숫자 연산에는 별도 int 변환이 필요하다.
  • 같은 패턴을 전체 입력에 fullmatch하면 앞 x=와 뒤 ;가 남기 때문에 실패한다. 부분 추출과 한 필드 전체 검증을 같은 성공 기준으로 사용하지 않는다.
  • (a)?는 빈 입력에서 선택 그룹이 참여하지 않아 group(1)이 None이다. (a*)는 그룹이 빈 문자열로 참여하므로 group(1)은 ""이고 은 (0, 0)이다.
import re
text = "x=12-AB;"
m = re.search(r"([0-9]+)-([A-Z]+)", text)
print(m.span(), m.span(1), m.span(2))
# (2, 7) (2, 4) (5, 7)
print(re.fullmatch(r"([0-9]+)-([A-Z]+)", text))  # None
print(re.fullmatch(r"(a)?", "").group(1))  # None
print(re.fullmatch(r"(a*)", "").span(1))  # (0, 0)

실행하지 않은 교육 예시. 주석의 출력은 언어 규칙으로 추적한 예상값이다.

검증·예외

06거의 맞는 입력·빈 일치·이스케이프를 진단하기

  • 공백·탭·개행 때문에 실패할 때는 repr로 문자 표현을 읽는다. 화면상 한 줄처럼 보여도 마지막 개행이나 보이지 않는 문자가 일치 조건을 바꿀 수 있다.
  • 결과가 None이면 불일치이지만 길이 0의 Match 객체는 성공한 일치다. group()의 빈 문자열만으로 성공 여부를 판정하지 않는다.
  • MULTILINE은 ^와 $의 줄 경계 의미를 바꾸고 DOTALL은 점의 개행 일치를 바꾼다. 두 옵션을 서로의 대체물로 켜면 예상하지 않은 넓은 일치를 만들 수 있다.
  • 중첩 반복이나 겹치는 대안은 끝부분에서 실패할 때 많은 조합을 다시 시도할 수 있다. 짧은 성공 예제만 통과한 사실은 최대 입력에서의 처리 시간을 증명하지 않는다.
  • re, GNU sed BRE·ERE는 연산자와 옵션의 표기가 다르다. 같은 글자 패턴을 복사한 뒤 결과가 달라지면 엔진·옵션·셸 인용의 세 경계를 따로 확인한다.

07IP처럼 보이는 문자열과 실제 주소 검증

  • [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+는 점으로 나뉜 숫자 네 묶음을 찾지만 999.999.999.999도 받아들일 수 있다.
    • 범위, 주소 버전, 앞뒤 경계까지 요구하면 단순한 추출과 의미 검증을 분리하는 편이 명확하다.
  • ipaddress 모듈은 · 주소 해석에 맞는 검증을 제공한다.
  • 으로 후보를 모은 뒤 각 후보를 주소 객체로 해석하는 구조를 사용할 수 있다.
  • 에 사용자 입력을 그대로 넣으면 입력의 .·*·괄호가 패턴 문법이 된다.
  • 문자 그대로 찾는 것이 목적이면 re.escape를 적용하거나 문자열 검색을 선택한다.
  • 치환 문자열에서도 역참조 문법이 따로 작동하므로 패턴과 대체값의 해석을 구분한다.
  • 입력값을 코드 문법으로 합치는 문제가 ·셸만의 문제가 아니라는 점이 연결된다.
import ipaddress
for text in ["192.0.2.10", "999.1.2.3"]:
    try:
        print(ipaddress.ip_address(text))
    except ValueError:
        print("주소 형식 또는 범위 오류")

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

08sed의 주소·명령·자동 출력과 BRE/ERE

  • sed는 입력 줄을 읽고 주소 조건에 해당하는 명령을 적용한 뒤 기본적으로 결과를 출력한다.
  • -n은 자동 출력을 끄고 p는 선택한 줄을 출력한다.
  • -i는 원본 파일 변경이므로 단순한 결과 미리보기와 다르다.
  • /error/d는 해당 패턴 줄을 출력 흐름에서 지우고 s/old/new/g는 한 줄의 일치 부분을 모두 치환한다.
    • g가 없으면 일반적으로 첫 일치만 바꾼다.
  • GNU sed의 기본 BRE와 -E의 ERE는 +·?·괄호 등의 표기가 다르다.
  • BRE에서 \+ 같은 표기는 구현 확장일 수 있으므로 re의 패턴을 그대로 붙이지 않는다.
  • ^$는 길이가 0인 빈 줄이고 ^[[:space:]]*$는 공백류만 있는 줄까지 포함한다.
  • 아래 명령들은 원본을 수정하지 않고 화면에 처리 결과를 출력한다.
  • 실제 설정 변경은 출력과 대상 범위를 검토한 뒤 별도 단계로 다룬다.
bash
sed -n '1,5p' lesson.log
sed -n '/ERROR/p' lesson.log
sed -E 's/port=[0-9]+/port=<number>/g' lesson.log
sed '/^[[:space:]]*$/d' lesson.log

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

09입력 크기와 성능

  • 중첩 반복과 애매한 선택을 가진 일부 패턴은 특정 입력에서 과도한 되돌아가기를 일으킬 수 있다.
  • 처리할 줄 길이와 입력 크기를 제한하고 대표적인 정상·오류·긴 입력에서 확인한다.
  • 전체가 필요한지 ASCII 숫자만 필요한지도 명확히 한다.
import re

line = "status=200 duration_ms=12"
match = re.fullmatch(r"status=(?P<status>[0-9]{3}) duration_ms=(?P<ms>[0-9]+)", line)
print(match.groupdict() if match else "형식 불일치")

고정된 모의 로그 한 줄의 전체 형식을 확인하고 필드를 추출한다.

10한 줄의 로그가 구조화된 값이 되는 과정

  • 고정 형식의 로그 status=200 duration_ms=12를 읽는 예에서는 우선 전체 문자열이 기대한 형식에 맞는지 확인한다.
  • fullmatch에 숫자 세 자리와 시간 숫자 필드를 지정하면 앞뒤의 불필요한 문자열도 함께 거부할 수 있다.
  • 일치 객체에서 이름 있는 그룹을 얻으면 status와 ms를 구분한 문자열 값을 받는다.
  • 그룹이 숫자에 맞았더라도 결과는 자동으로 정수가 되지 않으므로 다음 단계에서 필요한 변환과 범위 검사를 수행한다.
  • 같은 패턴을 로 사용하면 문자열 일부만 맞아도 성공할 수 있어 주석이나 공격적인 접두·접미 자료가 남은 입력까지 통과시킬 수 있다.
  • match는 시작 위치에서만 찾지만 전체를 소비했다는 뜻은 아니다.
  • 끝 고정 기호와 줄바꿈 옵션에도 세부 차이가 있으므로 전체 입력 검증에는 fullmatch의 의도가 더 분명하다.
  • 반면 로그 안에서 특정 조각을 찾아내는 목적이라면 가 맞을 수 있다.
    • 도구 선택은 검증인지 추출인지에 따라 달라진다.
  1. 과도하게 긴 줄은 처리 정책에 따라 제한한다.
  2. 문자열 전체가 지정된 구조에 맞는지 확인한다.
  3. status와 ms라는 이름으로 값을 얻는다.
  4. 원하는 숫자 자료형과 업무 범위를 확인한다.

설명과 함께 보는 보조 도식

처리 과정

모의 로그에서 숫자 필드 추출하기

status=200 duration_ms=12를 한 줄 전체로 검증한다.

전체 단계를 한 번에 읽기
  1. 01

    입력 준비

    과도하게 긴 줄은 처리 정책에 따라 제한한다.

    원문
    status=200 duration_ms=12
  2. 02

    fullmatch

    문자열 전체가 지정된 구조에 맞는지 확인한다.

    부분만 일치
    검증 실패
  3. 03

    그룹 추출

    status와 ms라는 이름으로 값을 얻는다.

    status
    "200"
    ms
    "12"
  4. 04

    변환·범위 검사

    원하는 숫자 자료형과 업무 범위를 확인한다.

    완료
    형식과 의미를 모두 확인
학습 모식도. 선택한 입력과 조건에서 발생하는 처리·상태 변화를 보여 주며 실제 명령은 실행하지 않는다.

11패턴을 읽을 때 확인할 경계

  • 점과 반복 기호의 조합은 예상보다 넓은 범위를 잡을 수 있다.
  • 탐욕적 반복과 비탐욕적 반복을 바꿔도 중첩된 문법이나 이스케이프를 자동으로 이해하지는 않는다.
  • 중괄호가 들어 있다고 JSON 전체를 으로 안정적으로 읽을 수 있는 것은 아니므로 자료 형식에 맞는 파서를 먼저 사용한다.
  • 를 처리하는 패턴에서 숫자·단어 문자 클래스의 범위를 확인하고, ASCII 숫자만 허용하려는 필드라면 [0-9]처럼 의도를 명시한다.
  • 정상 입력만으로 확인하면 거의 맞지만 끝에서 실패하는 긴 입력의 비용을 놓칠 수 있다.
  • 애매한 대안과 중첩 반복을 피하고 예상 최대 길이·줄바꿈·빈 값·구분자 포함 값으로 결과를 비교한다.
  • 작은 모의 자료에서는 일치 범위와 그룹값을 함께 출력해 어디까지 소비했는지 확인할 수 있다.
  • 실패 결과가 None일 때 group을 바로 호출하면 별도 오류가 생기므로 성공 여부를 먼저 확인한다.
  • 은 인증이나 접근 통제를 대신하는 기능이 아니라 입력을 해석하는 한 단계다.
구성·관찰 대상확인하는 값구분할 문제
패턴문자 클래스·반복·옵션의도보다 넓은 일치
일치 범위부분·시작·전체검증에 부분 검색을 사용했는지
그룹 결과문자열 값·이름·위치숫자 변환 전 상태
실패 입력길이·후반 불일치과도한 되돌아가기 비용
학습 확인

이해했는지 확인하기

  • 부분 검색과 전체 문자열 검증을 구분한다.
  • 패턴 일치 뒤 의미와 범위도 확인한다.
  • 긴 입력과 실패 입력에서 처리 비용을 검토한다.

함께 확인할 공식 자료