빌드노트
Select-String에서 api.example이 apiXexample까지 잡히는 이유
Select-String은 따옴표 안의 검색어도 기본적으로 정규식으로 읽는다. 합성 로그에서 점·대괄호와 대소문자 조건을 실제 비교해, 문자열 포함 검색에는 SimpleMatch를 쓰고 정규식 안의 고정 값에는 Regex.Escape를 적용하는 기준을 설명한다.
로그에서 api.example을 찾으려고 Select-String -Pattern 'api.example'을 실행했는데 apiXexample이 든 줄도 나온다면, 점이 정규식으로 해석된 것이다. 입력한 문자를 그대로 포함하는 줄을 찾으려면 -SimpleMatch를 붙인다. 정규식 조건을 함께 써야 할 때는 고정 문자열 부분에만 [regex]::Escape()를 적용한다.
로그가 있는 폴더를 PowerShell에서 열고, 아래 app.log를 자신의 파일 이름으로 바꿔 실행한다. 이 명령은 파일 내용에서 api.example이라는 문자열을 포함한 줄과 그 줄 번호를 보여 준다.
$logPath = '.\app.log'
Select-String -LiteralPath $logPath -Pattern 'api.example' -SimpleMatch |
Select-Object LineNumber, Line
-LiteralPath는 파일 경로를 그대로 지정하고, -SimpleMatch는 파일 내용의 검색어를 그대로 찾는다. 경로에 -LiteralPath를 썼어도 -Pattern의 정규식 해석은 별도로 남는다. 파일을 읽지 못했다는 오류가 나오면 경로와 접근 권한부터 확인한다. 읽기 오류 없이 결과가 없다면 그 파일의 실제 문구와 검색어를 다시 대조한다.
기본 검색은 대소문자를 구분하지 않으므로 API.EXAMPLE도 포함한다. 대소문자까지 같아야 한다면 -CaseSensitive를 추가한다. Select-String의 Pattern·SimpleMatch·CaseSensitive 설명 (새 탭에서 열림)
따옴표가 있어도 점의 의미는 남는다
작은따옴표는 PowerShell이 문자열 안의 변수를 치환하지 않게 한다. 그 문자열을 전달받은 명령이 정규식으로 해석하는 것까지 막지는 않는다. 이 예의 'api.example'을 "api.example"로 바꾸거나 변수에 넣어 전달해도 같은 줄들이 나왔다. PowerShell의 인용부호 규칙 (새 탭에서 열림)
차이를 확인하기 위해 Windows의 PowerShell 7.6.6, .NET 10.0.12, ko-KR 문화권에서 새 합성 로그를 검색했다. 기존 업무 로그를 사용한 사례가 아니다. 아래 배열은 그 로그의 여섯 줄이다. 파일을 만들지 않고 같은 PowerShell 세션에서 실행하면 세 검색의 행 번호를 비교할 수 있다.
$lines = @(
'INFO api.example code=[42]'
'INFO apiXexample code=42'
'INFO API.EXAMPLE code=[42]'
'INFO api.example code=4'
'INFO other code=[99]'
'INFO myapi.example code=[42]extra'
)
$lines | Select-String -Pattern 'api.example' | Select-Object -ExpandProperty LineNumber
$lines | Select-String -Pattern 'api.example' -SimpleMatch | Select-Object -ExpandProperty LineNumber
$lines | Select-String -Pattern 'api.example' -SimpleMatch -CaseSensitive | Select-Object -ExpandProperty LineNumber
첫 검색은 1·2·3·4·6행, -SimpleMatch 검색은 1·3·4·6행을 반환했다. 정규식의 점은 기본적으로 줄바꿈 문자 \n을 제외한 한 문자에 맞으므로, 2행의 X도 통과한다. 리터럴 점만 찾는 정규식 표기는 \.이다. .NET 정규식의 점과 문자 클래스 (새 탭에서 열림)
세 번째 검색은 대소문자를 구분하므로 1·4·6행만 반환했다. 6행이 계속 잡히는 것도 확인할 부분이다. myapi.example 안에도 api.example이 들어 있다. -SimpleMatch는 문자열을 포함하는지 검사하며, 단어 전체나 줄 전체가 같은지를 검사하는 옵션은 아니다.
대괄호는 결과가 더 달라진다
같은 $lines에서 [42]를 검색하면 대괄호까지 있는 문구와 숫자 한 글자를 찾는 패턴의 차이가 드러난다. 아래 세 명령은 기본 정규식, 문자열 검색, 이스케이프한 정규식 순서다.
$lines | Select-String -Pattern '[42]' | Select-Object -ExpandProperty LineNumber
$lines | Select-String -Pattern '[42]' -SimpleMatch | Select-Object -ExpandProperty LineNumber
$lines | Select-String -Pattern ([regex]::Escape('[42]')) | Select-Object -ExpandProperty LineNumber
기본 정규식은 1·2·3·4·6행, 나머지 두 검색은 모두 1·3·6행을 반환했다. 정규식 [42]는 4 또는 2 한 글자에 맞는다. 따라서 대괄호가 없는 code=42와 code=4도 잡힌다. 대괄호를 포함한 네 글자 [42]가 목적이면 -SimpleMatch가 바로 그 의도를 표현한다.
[regex]::Escape()는 문자열의 정규식 특수 문자를 필요한 만큼 이스케이프한다. 실행 환경에서 [42]는 \[42], api.example은 api\.example로 바뀌었다. 점 패턴도 이스케이프한 뒤 정규식 검색을 하면 -SimpleMatch와 같은 1·3·4·6행이 나왔다. Regex.Escape의 동작 (새 탭에서 열림)
두 방법을 겹쳐 적용하지는 않는다. 이스케이프한 api\.example에 -SimpleMatch를 붙인 실험에서는 결과가 없었다. 이때는 백슬래시까지 실제 문자로 찾는데, 입력 로그에는 그 백슬래시가 없기 때문이다.
정규식 조건이 필요할 때만 일부를 Escape한다
code=[42] 뒤가 공백이거나 줄 끝인 경우만 찾고 싶다면 문자열 포함 검색만으로는 부족하다. 앞서 만든 $lines에 다음 검색을 적용하면, 고정 값 [42]는 문자 그대로 찾으면서 뒤의 경계 조건은 정규식으로 유지할 수 있다.
$needle = '[42]'
$pattern = 'code=' + [regex]::Escape($needle) + '(?:\s|$)'
$lines | Select-String -Pattern $pattern | Select-Object -ExpandProperty LineNumber
결과는 1·3행이었다. (?:\s|$)는 뒤에 공백 문자가 오거나 줄이 끝나는 조건이므로, [42]extra가 있는 6행은 제외됐다. 이처럼 조건을 직접 작성할 때는 값인 $needle만 이스케이프한다. 완성한 패턴 전체를 이스케이프하면 의도한 정규식 조건도 문자 그대로 찾게 된다. 정규식 조건이 전혀 필요 없다면 원래 검색어에 -SimpleMatch를 쓰면 된다.
직접 확인한 범위는 위 여섯 줄의 ASCII 검색어와 해당 Windows 환경이다. Windows PowerShell 5.1·다른 운영체제, 문화권별 비ASCII 대소문자 비교, 대용량 로그의 처리 시간은 실행해 비교하지 않았다.