반응형

바쁜 분은 이것만

  1. *, ?, [ ]가 들어간 부분을 따옴표로 감싸기: grep -ril "foo" . --include='*.md'
  2. find도 똑같이: find . -name '*.jpg'
  3. 대괄호 패키지 설치도: pip install "requests[socks]"

맥 터미널에서 명령을 실행했는데 결과 대신 이런 한 줄만 나오고 끝날 때가 있습니다.

zsh: no matches found: --include=*.md

오늘 제 맥에서 여러 폴더의 md·csv 파일에서 글자를 찾으려고 grep -ril "..." ~/폴더 --include=*.md --include=*.csv를 실행했다가 실제로 이 메시지를 만났습니다. grep이 고장 난 게 아니라 grep이 실행되기도 전에 zsh가 멈춘 것이에요. 아래 출력은 모두 제 맥(macOS 26.6.2, zsh 5.9)에서 임시 폴더에 예제 파일을 만들어 직접 재현한 결과입니다.

1. 에러 원인: 별표(*)는 zsh가 먼저 처리한다

터미널에 *.md라고 치면, 이 글자는 프로그램에 그대로 전달되지 않습니다. 셸(zsh)이 먼저 "지금 폴더에서 이름이 .md로 끝나는 파일"을 찾아 실제 파일 이름 목록으로 바꿔치기한 다음 프로그램을 실행해요. 이걸 글로브(glob), 우리말로는 와일드카드 확장이라고 부릅니다. *뿐 아니라 ?(한 글자)와 [abc](괄호 안 글자 중 하나)도 같은 대상이에요.

문제는 바꿔칠 파일이 하나도 없을 때입니다. zsh는 기본 설정에서 "맞는 파일이 없다"를 에러로 보고 명령 자체를 실행하지 않습니다. 그게 바로 no matches found예요. --include=*.md라는 글자 전체를 "이름이 --include=로 시작하고 .md로 끝나는 파일"로 해석했는데, 그런 파일이 있을 리 없으니 멈춘 거죠.

2. 직접 재현해 보기

예제 폴더 구조는 이렇습니다. 현재 폴더에는 파일이 없고, 하위 폴더에만 있어요.

$ ls -R
notes
photos

./notes:
a.md
b.csv
c.txt

./photos:
sub
y.jpg

./photos/sub:
x.jpg

여기서 세 가지를 실행해 봤습니다.

$ grep -ril foo . --include=*.md --include=*.csv
zsh: no matches found: --include=*.md

$ ls *.xyz
zsh: no matches found: *.xyz

$ echo requests[socks]
zsh: no matches found: requests[socks]

세 번째가 눈여겨볼 부분입니다. 별표가 없어도 대괄호 때문에 같은 에러가 나요. 그래서 pip install requests[socks]처럼 "추가 기능 포함" 패키지를 설치할 때도 zsh에서는 pip가 실행되기 전에 막힙니다. (네트워크 없이 확인하려고 여기서는 echo로 재현했어요.)

3. 해결 방법

① 따옴표로 감싸기 (가장 추천)

작은따옴표든 큰따옴표든, 따옴표 안의 *는 zsh가 건드리지 않고 그대로 프로그램에 넘깁니다. 그러면 grep이 직접 "*.md"를 패턴으로 받아서 하위 폴더까지 알아서 찾아요.

$ grep -ril foo . --include='*.md' --include='*.csv'
./notes/b.csv
./notes/a.md

$ grep -ril foo . --include="*.md"
./notes/a.md

$ echo "requests[socks]"
requests[socks]

② 백슬래시로 한 글자만 막기

특수문자 앞에 \를 붙이면 그 글자만 "그냥 글자"로 취급됩니다. 따옴표가 번거로운 짧은 명령에 쓸 만해요.

$ grep -ril foo . --include=\*.md
./notes/a.md

③ noglob: 이 명령 한 번만 글로브 끄기

명령 맨 앞에 noglob을 붙이면 그 줄에서만 와일드카드 확장을 하지 않습니다. 따옴표를 여러 개 붙이기 귀찮을 때 편해요. zsh 전용이라 bash에서는 안 됩니다.

$ noglob grep -ril foo . --include=*.md --include=*.csv
./notes/b.csv
./notes/a.md

④ setopt nonomatch: 동작 방식 자체를 바꾸기 (비추천)

검색하면 ~/.zshrc에 setopt nonomatch를 넣으라는 답이 많이 나옵니다. 이 옵션을 켜면 맞는 파일이 없을 때 에러 대신 글자를 그대로 넘겨 줍니다.

$ setopt nonomatch
$ ls *.xyz
ls: *.xyz: No such file or directory
$ grep -ril foo . --include=*.md
./notes/a.md

에러는 사라지지만 저는 권하지 않습니다. 이유는 두 가지예요. 첫째, .zshrc에 넣으면 모든 터미널, 모든 명령의 동작이 바뀌는 전역 설정입니다. 둘째, 맞는 파일이 있으면 여전히 바꿔치기가 일어나서 아래 4번 같은 함정은 그대로 남아요. 에러를 숨길 뿐 원인을 고치는 게 아닙니다. 그래서 "패턴은 따옴표로 감싼다"는 습관 하나가 제일 확실합니다.

4. find의 함정: 될 때도 있고 안 될 때도 있다

find . -name *.jpg는 특히 헷갈립니다. 지금 있는 폴더에 jpg가 있느냐에 따라 결과가 달라지거든요. 같은 명령을 세 가지 상황에서 실행해 봤습니다.

(가) 현재 폴더에 jpg가 없을 때 → 에러

$ find . -name *.jpg
zsh: no matches found: *.jpg

(나) 현재 폴더에 jpg가 1개(y.jpg) 있을 때 → 조용히 틀린 결과

$ cd photos
$ find . -name *.jpg
./y.jpg

에러 없이 실행됐지만 sub/x.jpg가 빠졌습니다. zsh가 *.jpg를 y.jpg로 바꿔치기해서, 실제로는 find . -name y.jpg가 실행된 거예요. 에러가 안 나니 결과가 틀린 줄도 모르기 쉽습니다.

(다) 현재 폴더에 jpg가 2개 이상 있을 때 → 알 수 없는 에러

$ find . -name *.jpg
find: z.jpg: unknown primary or operator

find . -name y.jpg z.jpg로 바뀌어서 find가 남는 이름을 이해하지 못한 겁니다. 따옴표로 감싸면 세 상황 모두 하위 폴더까지 정확하게 찾아요.

$ find . -name "*.jpg"
./y.jpg
./sub/x.jpg

5. bash에서는 왜 그냥 됐을까

인터넷에서 복사한 명령이 글쓴이 화면에서는 잘 되는데 내 맥에서만 안 되는 경우가 많습니다. 맥은 macOS 카탈리나부터 기본 셸이 bash에서 zsh로 바뀌었고, 예전 자료 상당수가 bash 기준이기 때문이에요. 맥에 들어 있는 bash(3.2.57)로 같은 명령을 실행하면 이렇습니다.

$ bash -c 'ls *.xyz; echo requests[socks]; grep -ril foo . --include=*.md'
ls: *.xyz: No such file or directory
requests[socks]
./notes/a.md

bash는 맞는 파일이 없으면 패턴 글자를 그대로 넘깁니다. 그래서 grep이 *.md를 받아 정상 동작했어요. zsh의 nonomatch와 같은 방식이죠. 반대로 zsh는 "의도와 다르게 실행될 바엔 멈춘다"는 쪽을 택한 셈입니다. 참고로 bash에서도 맞는 파일이 있으면 바꿔치기는 똑같이 일어나서, 위 (다) 상황에서는 bash도 find: z.jpg: unknown primary or operator가 났습니다. 셸과 상관없이 따옴표가 정답인 이유예요.

정리

  • zsh: no matches found = 명령이 실행되기 전에 zsh가 *, ?, [ ]를 파일 이름으로 바꾸려다 맞는 파일이 없어서 멈춘 것
  • 해결 1순위: 패턴을 따옴표로 감싸기 ('*.md', "*.jpg", "pkg[extra]")
  • 가끔은 \*(백슬래시)나 noglob 명령도 OK
  • setopt nonomatch는 전역 설정이고 함정을 숨길 뿐이라 비추천
  • find -name *.jpg는 현재 폴더 상황에 따라 에러·틀린 결과·다른 에러가 나니 항상 따옴표
반응형

+ Recent posts