본문 바로가기
빌드노트 검색

빌드노트

PowerShell -contains가 파일 이름 속 단어를 찾지 못하는 이유

파일 이름에 검색어가 보이는데 PowerShell -contains가 False라면 전체 항목과 부분 문자열을 구별해야 한다. 같은 문자열과 배열에서 -contains, -like, String.Contains의 값·반환형을 비교하고 대소문자와 대괄호 검색어에 맞는 선택을 설명한다.

파일 이름이 invoice-2026.csv인데 $name -contains 'invoice'가 False를 반환할 수 있다. -contains는 문자열 안의 글자를 찾는 연산자가 아니라, 왼쪽 값에 오른쪽 값과 같은 항목이 있는지 검사하는 연산자다. 이름 일부를 찾으려면 문자열의 .Contains()나 와일드카드 패턴을 받는 -like를 고른다.

먼저 비교하는 변수가 단일 문자열인지 배열인지 확인한다. 아래는 실제 파일을 읽지 않고 만든 이름으로 비교한 예제다. Windows의 PowerShell 7.6.6, .NET 10.0.12에서 실행했다. PowerShell 7이 설치된 터미널에서 pwsh -NoProfile로 새 세션을 열고 다음 코드를 입력할 수 있다.

$name = 'invoice-2026.csv'
$term = 'invoice'
$name.GetType().FullName  # System.String

$name -contains $term    # False
$name -like "*$term*"     # True
$name.Contains($term)    # True

세 결과는 모두 System.Boolean이었다. 단일 문자열에 -contains를 써도 그 안을 잘라서 비교하지 않는다. 여기서는 문자열 전체가 하나의 비교 대상이므로 $name -contains 'invoice-2026.csv'처럼 전체 이름을 주면 True다.

-like는 오른쪽을 문자열 전체에 맞출 패턴으로 읽는다. 양옆의 *가 앞뒤에 다른 글자가 있어도 된다는 뜻이다. 따라서 $name -like $term처럼 별표를 빼면 이 입력에서는 False다. 반면 문자열의 .Contains($term)는 검색어 자체가 연속해서 들어 있는지 검사하므로 별표를 붙이지 않는다.

이 구분은 PowerShell 비교 연산자 문서 (새 탭에서 열림)의 포함 연산과 패턴 비교 설명에 해당한다. -contains가 False라는 이유만으로 파일 이름의 공백이나 인코딩부터 의심할 필요는 없다. 우선 그 조건이 전체 항목을 찾으려는 것인지, 이름 일부를 찾으려는 것인지 확인한다.

배열로 바꾸면 반환값도 달라진다

같은 이름을 배열에 넣어도 -contains 'invoice'가 부분 검색으로 바뀌지는 않는다. 다음 코드는 전체 이름의 존재 여부와 패턴에 맞는 이름 목록을 나란히 확인한다.

[string[]]$names = @($name, 'readme.txt', 'invoice-draft.csv')
$names.GetType().FullName             # System.String[]
$names -contains $term                # False
$names -contains 'invoice-2026.csv'    # True

$found = $names -like "*$term*"
$found
# invoice-2026.csv
# invoice-draft.csv
$found.GetType().FullName             # System.Object[]
$found.Count -gt 0                    # True

-contains는 배열에서도 Boolean을 반환한다. invoice라는 항목은 없고 invoice-2026.csv라는 항목은 있으므로 앞의 두 결과가 갈린다. $names -contains '*invoice*'로 바꿔도 별표를 패턴으로 처리하지 않아 False다.

배열 왼쪽의 -like는 일치한 문자열들을 돌려준다. 위 실행에서는 두 이름이 담긴 System.Object[]였으며, Boolean True를 반환한 것이 아니다. 일치 항목이 있는지만 필요하면 위처럼 결과의 개수를 비교한다. 같은 배열에서 $names -like '*absent*'는 빈 배열이었고, 그 결과의 .Count -gt 0은 False였다.

검색어를 패턴으로 해석하고 싶지 않다면 각 문자열에 .Contains()를 적용해 이름을 고른다. 아래의 $_는 배열에서 하나씩 전달되는 이름이다. @(...)는 검색 결과가 몇 개든 배열로 받기 위해 사용했다.

$literalFound = @($names | Where-Object { $_.Contains($term) })
$literalFound
# invoice-2026.csv
# invoice-draft.csv

대소문자 조건은 검색 방식과 따로 고른다

소문자 invoice에서는 두 부분 검색이 모두 성공했지만, 검색어를 대문자로 바꾸면 결과가 달라진다. 다음 비교에서는 파일 이름과 배열은 그대로 두고 검색 조건만 바꾼다.

$name -like '*INVOICE*'                 # True
$name -clike '*INVOICE*'                # False
$name.Contains('INVOICE')               # False
$name.Contains('INVOICE', [StringComparison]::OrdinalIgnoreCase)
# True

$names -contains 'INVOICE-2026.CSV'      # True
$names -ccontains 'INVOICE-2026.CSV'     # False

-like와 -contains의 문자열 비교는 기본적으로 대소문자를 구분하지 않고, -clike와 -ccontains는 구분한다. 문자열의 인자 하나짜리 .Contains()는 기본적으로 대소문자를 구분하므로, 영문 대소문자를 무시할 의도라면 위처럼 비교 규칙을 전달한다. String.Contains 공식 문서 (새 탭에서 열림)는 기본 비교와 StringComparison을 받는 오버로드를 구분한다. 여기서 두 인자를 전달한 호출은 PowerShell 7 환경에서 확인한 코드다.

배열에서 이름을 고를 때도 앞선 Where-Object 안의 호출을 $_.Contains($term, [StringComparison]::OrdinalIgnoreCase)로 바꿀 수 있다. 이번 배열에 대문자 INVOICE를 검색했을 때도 두 이름이 선택됐다.

검색어의 대괄호까지 글자로 찾으려면

외부에서 받은 검색어를 "*$term*"에 넣을 때는 그 값 자체에 와일드카드가 있는지도 생각해야 한다. 앞서 만든 $name에는 대괄호가 없다. 검색어만 [2026]으로 바꿔 두 방식을 비교한다.

$term = '[2026]'
$name -like "*$term*"                    # True
$name.Contains($term)                   # False
'invoice-[2026].csv'.Contains($term)     # True

-like에서 [2026]은 대괄호를 포함한 여섯 글자가 아니라, 2, 0, 6 중 한 글자에 맞는 패턴이다. 그래서 대괄호가 없는 invoice-2026.csv도 통과한다. 문자열을 따옴표로 감싸는 것만으로 이 패턴 의미가 사라지지는 않는다. *와 ?도 패턴 문자로 쓰인다.

사용자가 입력한 글자 그대로를 이름 안에서 찾으려는 경우에는 문자열의 .Contains()를 사용한다. 이 비교에서 False는 [2026]이라는 연속된 문자열이 없다는 뜻이고, 실제 대괄호를 넣은 마지막 이름에서는 True다. 전체 이름이 목록에 있는지를 묻는 조건이라면 계속 -contains를 쓰면 된다.

여기서 확인한 범위는 합성한 문자열과 문자열 배열이다. 실제 파일 객체나 파일시스템의 이름 비교 규칙은 검사하지 않았다. Windows PowerShell 5.1에서의 실행과 비영문 문자열의 문화권별 비교 차이도 검증하지 않았다.

← 전체 글 보기