단순한 후보 추출 패턴
IPv4 주소처럼 보이는 문자열은 점으로 나뉜 숫자 네 묶음으로 찾을 수 있어. \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}에 앵커를 붙이면 ^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$가 돼.
하지만 이 패턴은 실제 IP 주소가 아닌 999.999.999.999도 받아들여. 각 옥텟의 유효 범위는 0부터 255까지이기 때문이야.
엄격한 패턴
각 옥텟을 0부터 255까지로 제한하려면 다음과 같이 범위를 나눠 표현할 수 있어.
(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])
이 표현은 처음부터 [0-9]를 사용해 ASCII 숫자만 허용해. 25[0-5]는 250부터 255까지, 2[0-4][0-9]는 200부터 249까지, 1[0-9]{2}는 100부터 199까지, [1-9]?[0-9]는 0부터 99까지를 나타내.
네 옥텟과 문자열 전체의 경계를 합친 패턴은 다음과 같아.
^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$
이 전체 패턴도 ASCII 숫자만 허용해. 다만 실서비스에서는 표준 라이브러리를 사용하는 편이 더 정확해.
표준 라이브러리에 검증 맡기기
Python의 ipaddress.ip_address(s) 같은 IP 주소 파서는 IPv4와 IPv6를 모두 다루고, CIDR 표기와 여러 예외 규칙도 알고 있어. 정규식은 IP 주소처럼 보이는 문자열을 찾는 데 사용하고, 실제로 유효한 주소인지는 전용 라이브러리로 검증해.
IPv6는 정규식으로 직접 검증하지 마
IPv6에는 16진수 그룹뿐 아니라 :: 압축과 IPv4가 섞인 표기 등 여러 형태가 있어. 이를 모두 처리하는 정규식은 길고 복잡해지면서도 유효한 형태를 빠뜨리기 쉬워. Python에서는 ipaddress, Go에서는 net.ParseIP 같은 전용 도구를 사용해.