반응형

바쁜 분은 이것만

  1. mdfind -onlyin 폴더 -name 단어 = Spotlight 색인으로 파일 이름 검색
  2. 조건은 'kMDItemFSName == "*.py"', 날짜는 $time.today(-7), 개수만은 -count
  3. 숨김 폴더(.venv 등)·색인 안 된 곳은 못 찾음 → 그때는 find

터미널에서 파일을 찾을 때 보통 find를 쓰죠. 그런데 큰 폴더에서는 디스크를 하나하나 훑느라 몇 초씩 걸려요. 맥에는 Spotlight가 미리 만들어 둔 색인이 있어서, mdfind로 그 색인을 바로 물어볼 수 있습니다. 파일 이름뿐 아니라 종류, 수정 날짜, 사진의 카메라 정보 같은 메타데이터로도 찾을 수 있어요.

예제는 테스트 폴더를 하나 만들어 그 안에서만 돌렸어요.

$ ls -R testdir
hello_mdfind.py
meeting_notes.txt
memo_launchd.txt
sub

testdir/sub:
deep
readme_mdfind.md
sample_mdfind.png

testdir/sub/deep:
config_mdfind.json

1. find와 속도 비교

같은 조건으로 개수와 걸린 시간만 찍어 봤어요(파이썬 time.perf_counter로 잼).

find   /System/Library    10776개  3.93초
mdfind /System/Library     1919개  0.33초
find   ~/blog-auto         1841개  0.04초
mdfind ~/blog-auto          228개  0.34초

두 가지가 보여요.

  • 큰 곳에서는 mdfind가 훨씬 빠르다. 몇 번 돌려도 find는 4~5초, mdfind는 0.3초대였어요. 반대로 작은 폴더에서는 find가 더 빨라요. mdfind는 색인 서버에 물어보는 시간이 늘 0.3초쯤 들기 때문이에요.
  • 개수가 다르다. ~/blog-auto의 차이 1613개는 전부 .venv 안의 파일이었어요. Spotlight는 점(.)으로 시작하는 숨김 폴더를 색인하지 않아서, 숨김 폴더를 빼고 find로 세면 mdfind와 같은 228개가 나옵니다. 시스템 폴더도 색인 안 하는 곳이 많아요.
$ find ~/blog-auto -name '*.py' -not -path '*/.*' | wc -l
     228

그래서 "내 문서·다운로드 같은 데서 빨리 찾기"는 mdfind, "빠짐없이 전부"는 find로 나눠 쓰면 됩니다.

2. 기본: -onlyin, -name, -count

범위를 안 주면 디스크 전체를 찾으니 -onlyin으로 폴더를 정해 주는 게 좋아요. -name은 파일 이름에 그 글자가 들어 있으면 다 찾아요(하위 폴더 포함).

$ mdfind -onlyin testdir -name mdfind 2>/dev/null
~/work/testdir/hello_mdfind.py
~/work/testdir/sub/readme_mdfind.md
~/work/testdir/sub/deep/config_mdfind.json
~/work/testdir/sub/sample_mdfind.png

$ mdfind -onlyin testdir -count -name mdfind 2>/dev/null
4

2>/dev/null을 붙인 이유는 이런 줄이 같이 나와서예요. 에러가 아니라 한국어 검색어 해석기를 불러온다는 로그라, 출력만 필요하면 버리면 됩니다.

2026-11-03 00:18:53.474 mdfind[10000:2795348] [UserQueryParser] Loading keywords and predicates for locale "ko_KR"

옵션 없이 단어만 주면 파일 내용까지 찾아요. 텍스트 안에 "kickstart"가 적힌 메모가 나왔어요.

$ mdfind -onlyin testdir kickstart
~/work/testdir/memo_launchd.txt

3. 쿼리 문법: kMDItemFSName, kMDItemContentType

더 정확하게 찾으려면 속성 == "값" 쿼리를 씁니다. 셸이 *나 $를 건드리지 않게 작은따옴표로 통째로 감싸요.

$ mdfind -onlyin testdir 'kMDItemFSName == "*.py"'
~/work/testdir/hello_mdfind.py

$ mdfind -onlyin testdir 'kMDItemFSName == "MEETING*"c'
~/work/testdir/meeting_notes.txt
  • kMDItemFSName은 파일 이름 전체와 비교해요. *로 패턴을 만듭니다.
  • 값 뒤의 c는 대소문자 무시예요. 대문자 MEETING으로도 찾았어요.

파일 종류는 확장자 대신 UTI로 찾아요. kMDItemContentType은 딱 그 종류, kMDItemContentTypeTree는 상위 종류까지 포함이에요. 그래서 public.text로 찾으면 txt뿐 아니라 py·md·json도 같이 나옵니다.

$ mdfind -onlyin testdir 'kMDItemContentType == "public.png"'
~/work/testdir/sub/sample_mdfind.png

$ mdfind -onlyin testdir 'kMDItemContentTypeTree == "public.text"'
~/work/testdir/memo_launchd.txt
~/work/testdir/meeting_notes.txt
~/work/testdir/hello_mdfind.py
~/work/testdir/sub/readme_mdfind.md
~/work/testdir/sub/deep/config_mdfind.json

조건은 &&, ||로 묶어요.

$ mdfind -onlyin testdir 'kMDItemFSName == "*mdfind*" && kMDItemContentType == "public.python-script"'
~/work/testdir/hello_mdfind.py

4. 날짜 조건: $time.today(-7)

$time.today()는 오늘 0시, 괄호 안 숫자는 며칠 전/후예요. 테스트를 위해 meeting_notes.txt만 touch -t 202609010900으로 9월 1일로 돌려 두었어요.

$ mdfind -onlyin testdir 'kMDItemFSContentChangeDate >= $time.today(-7)'
~/work/testdir/sub
~/work/testdir/sub/deep
~/work/testdir/memo_launchd.txt
~/work/testdir/hello_mdfind.py
~/work/testdir/sub/readme_mdfind.md
~/work/testdir/sub/deep/config_mdfind.json
~/work/testdir/sub/sample_mdfind.png

$ mdfind -onlyin testdir 'kMDItemFSContentChangeDate < $time.today(-7)'
~/work/testdir/meeting_notes.txt

"최근 7일"에 폴더(sub, deep)도 끼어 있죠. 파일만 원하면 && kMDItemContentTypeTree == "public.text"처럼 종류 조건을 더하면 됩니다.

5. mdls로 메타데이터 보기

어떤 속성 이름을 쓸지 모르겠으면 mdls로 파일 하나의 메타데이터를 전부 봐요. 테스트 PNG(64×48)를 보면 이렇습니다(일부).

$ mdls testdir/sub/sample_mdfind.png
...
kMDItemContentType                     = "public.png"
kMDItemContentTypeTree                 = (
    "public.png",
    "public.image",
    "public.data",
    "public.item",
    "public.content"
)
...
kMDItemFSName                          = "sample_mdfind.png"
kMDItemFSSize                          = 154
kMDItemKind                            = "PNG 이미지"
kMDItemPixelHeight                     = 48
kMDItemPixelWidth                      = 64

필요한 것만 보려면 -name, 값만 받으려면 -raw예요.

$ mdls -name kMDItemContentType -name kMDItemPixelWidth -name kMDItemPixelHeight testdir/sub/sample_mdfind.png
kMDItemContentType = "public.png"
kMDItemPixelHeight = 48
kMDItemPixelWidth  = 64

$ mdls -raw -name kMDItemContentType testdir/hello_mdfind.py
public.python-script

여기서 본 이름(public.python-script)을 그대로 3번의 쿼리에 넣으면 돼요.

6. 사진 EXIF로 찾기: 카메라·렌즈

JPG 안의 EXIF(카메라 제조사·모델, 조리개, ISO 등)도 색인돼요. Pillow로 가짜 카메라 정보를 넣은 테스트 JPG를 만들어 확인했습니다.

$ mdls testdir/sub/exif_test.jpg | grep -E 'Acquisition|FNumber|FocalLength|ISO'
kMDItemAcquisitionMake                 = "TestMaker"
kMDItemAcquisitionModel                = "TEST CAM 1"
kMDItemFNumber                         = 2
kMDItemFocalLength                     = 35
kMDItemISOSpeed                        = 200

렌즈 이름(LensModel)도 넣었지만 이 테스트 파일에서는 kMDItemLensModel이 안 생겼어요. 이렇게 테스트로 만든 파일은 일부 항목이 색인되지 않을 수 있습니다. 찾은 파일이 많을 땐 경로를 다 출력하기보다 -count나 | wc -l로 개수부터 보는 게 편해요.

숫자 조건도 돼요. 예를 들어 'kMDItemFNumber <= 2'는 조리개 F2 이하로 찍은 사진이에요.

7. 새로 만든 파일이 안 나올 때: mdimport

색인은 파일이 생긴 뒤 조금 늦게 갱신돼요. 만들자마자 찾으면 0개였고, 1~2초 뒤에는 나왔어요.

$ echo hi > testdir/just_created_mdfind.txt; mdfind -onlyin testdir -count -name just_created
0
...
1

색인에서 계속 빠지는 파일이나 폴더는 mdimport 경로로 직접 색인을 요청할 수 있어요. 처음에 만든 테스트 폴더도 mdimport testdir을 한 번 해 두고 시작했습니다. 아예 안 나오면 그 폴더가 숨김 폴더이거나, 시스템 설정의 Spotlight 개인 정보 보호 목록에 들어 있는지 봅니다.

8. 파이썬 subprocess로 부르기

스크립트에서 쓸 때는 쿼리를 리스트 인자로 넘기면 셸 따옴표 걱정이 없어요. $time.today도 셸을 안 거치니 그대로 적습니다.

import subprocess
from pathlib import Path

TEST = Path(__file__).resolve().parent / "testdir"

def mdfind(query, onlyin=TEST):
    r = subprocess.run(["mdfind", "-onlyin", str(onlyin), query],
                       capture_output=True, text=True, check=True)
    return [Path(p) for p in r.stdout.splitlines()]

def mdcount(query, onlyin=TEST):
    r = subprocess.run(["mdfind", "-onlyin", str(onlyin), "-count", query],
                       capture_output=True, text=True, check=True)
    return int(r.stdout)

for p in mdfind('kMDItemContentTypeTree == "public.text" && kMDItemFSContentChangeDate >= $time.today(-7)'):
    print(p.relative_to(TEST))
print("이미지 개수:", mdcount('kMDItemContentTypeTree == "public.image"'))

# 메타데이터 값 하나만: mdls -raw
raw = subprocess.run(["mdls", "-raw", "-name", "kMDItemAcquisitionModel", str(TEST / "sub/exif_test.jpg")],
                     capture_output=True, text=True).stdout
print("카메라 모델:", raw)
memo_launchd.txt
hello_mdfind.py
sub/readme_mdfind.md
sub/deep/config_mdfind.json
이미지 개수: 2
카메라 모델: TEST CAM 1
  • stderr의 로그 줄은 capture_output=True로 따로 빠지니 stdout만 쓰면 돼요.
  • 9월로 돌려 둔 meeting_notes.txt는 날짜 조건에서 빠졌어요.
  • 정해진 시각마다 "최근 7일 파일 목록"을 남기고 싶다면 이 스크립트를 launchd에 올리면 됩니다.

정리

하고 싶은 것 명령
폴더 안에서 이름으로 mdfind -onlyin 폴더 -name 단어
개수만 mdfind -onlyin 폴더 -count '쿼리'
이름 패턴 (대소문자 무시) 'kMDItemFSName == "*.py"c'
종류로 'kMDItemContentTypeTree == "public.image"'
최근 7일 'kMDItemFSContentChangeDate >= $time.today(-7)'
카메라·렌즈 kMDItemAcquisitionModel, kMDItemLensModel
속성 이름 확인 mdls 파일, 값만은 mdls -raw -name 속성 파일
색인 다시 요청 mdimport 경로
숨김 폴더까지 빠짐없이 find

참고: Apple mdfind(1)·mdls(1)·mdimport(1) man page / Apple File Metadata Query Expression Syntax / Python 공식 문서 — subprocess.run

반응형

+ Recent posts