fd
CICD
Version info
[中文]
[한국어]
fd는 파일 시스템에서 항목을 찾는 프로그램입니다.
이는 find의 간단하고 빠르며 사용자 친화적인 대안입니다.
모든 find의 강력한 기능을 지원하는 것을 목표로 하지는 않지만, 대부분의 사용 사례에 대해 합리적인(주관적 의견이 담긴) 기본값을 제공합니다.
설치 • 사용 방법 • 문제 해결
기능 (Features)
- 직관적인 구문:
find -iname '*PATTERN*' 대신 fd PATTERN을 사용합니다.
- 정규 표현식(기본값) 및 glob 기반 패턴을 지원합니다.
- 병렬화된 디렉토리 순회 덕분에 매우 빠릅니다 (benchmark).
- 다른 파일 유형을 강조하기 위해 색상을 사용합니다(이는
ls와 동일합니다).
- 병렬 명령어 실행을 지원합니다.
- 스마트 케이스(Smart case): 기본적으로 검색은 대소문자를 구분하지 않습니다. 패턴에 대문자가 포함된 경우 대소문자를 구분하는 방식으로 전환됩니다*.
- 기본적으로 숨겨진 디렉토리와 파일을 무시합니다.
- 기본적으로
.gitignore의 패턴을 무시합니다.
- 명령어 이름이
find보다 50% 더 짧습니다* :-)
데모 (Demo)
Demo
사용 방법 (How to use)
먼저, 사용 가능한 모든 명령어 옵션에 대한 개요를 얻으려면, 간결한 도움말 메시지를 위해 fd -h를 실행하거나 더 자세한 버전을 위해 fd --help를 사용할 수 있습니다.
간단 검색
fd는 파일 시스템 내의 항목을 찾는 데 사용됩니다. 수행할 수 있는 가장 기본적인 검색은 검색 패턴 하나만 인수로 사용하여 fd를 실행하는 것입니다. 예를 들어, netflix라는 이름이 포함된 오래된 스크립트를 찾고 싶다고 가정해 봅시다:
> fd netfl
Software/python/imdb-ratings/netflix-details.py
이렇게 단일 인수로 호출하면, fd는 현재 디렉터리에서 패턴 netfl을 포함하는 모든 항목을 재귀적으로 검색합니다.
정규 표현식 검색
검색 패턴은 정규 표현식으로 처리됩니다. 여기서는 x로 시작하고 rc로 끝나는 항목을 검색해 보겠습니다:
> cd /etc
> fd '^x.*rc$'
X11/xinit/xinitrc
...
fd가 사용하는 정규 표현식 구문은 여기에 문서화되어 있습니다.
루트 디렉터리 지정
특정 디렉터리를 검색하고 싶다면, fd의 두 번째 인수로 제공할 수 있습니다:
> fd passwd /etc
/etc/default/passwd
/etc/pam.d/passwd
...
모든 파일 재귀적으로 나열하기
fd는 인자 없이 호출될 수 있습니다. 이는 현재 디렉터리의 모든 항목을 재귀적으로 빠르게 개요를 파악하는 데 매우 유용합니다 (이는 ls -R과 유사합니다):
> cd fd/tests
> fd
testenv
...
이 기능을 사용하여 주어진 디렉터리의 모든 파일을 나열하려면, . 또는 ^과 같은 와일드카드 패턴을 사용해야 합니다:
> fd . fd/tests/
testenv
testenv/mod.rs
...
특정 파일 확장자 검색하기
종종 우리는 특정 유형의 모든 파일에 관심이 있습니다. 이는 -e (또는 --extension) 옵션을 사용하여 수행할 수 있습니다. 여기서는 fd 저장소에서 모든 Markdown 파일을 검색합니다:
> cd fd
> fd -e md
CONTRIBUTING.md
...
-e 옵션은 검색 패턴과 조합하여 사용할 수 있습니다:
> fd -e rs mod
src/fshelper/mod.rs
src/lscolors/mod.rs
...
특정 파일 이름 검색하기
제공된 검색 패턴과 정확히 일치하는 파일을 찾으려면 -g 또는 --glob 옵션을 사용합니다:
> fd -g libc.so /usr
/usr/lib32/libc.so
/usr/lib/libc.so
숨겨진 및 무시된 파일 (Hidden and ignored files)
기본적으로 fd는 숨김 디렉토리를 검색하지 않으며, 검색 결과에 숨겨진 파일을 표시하지 않습니다. 이 동작을 비활성화하려면 -H 또는 --hidden 옵션을 사용할 수 있습니다:
> fd pre-commit
> fd -H pre-commit
.git/hooks/pre-commit.sample
작업 디렉토리가 Git 저장소이거나(또는 Git 저장소를 포함하는 경우), fd는 .gitignore 패턴 중 하나와 일치하는 폴더를 검색하지 않습니다 (파일도 표시하지 않습니다). 이 동작을 비활성화하려면 -I 또는 --no-ignore 옵션을 사용할 수 있습니다:
> fd num_cpu
> fd -I num_cpu
target/debug/deps/libnum_cpus-f5ce7ef99006aa05.rlib
모든 파일과 디렉토리를 정말로 검색하려면, 숨김 및 무시 기능을 결합하여 모든 것을 표시하는 (-HI) 방법을 사용하거나 -u/--unrestricted을 사용하면 됩니다.
전체 경로 일치 (Matching the full path)
기본적으로 fd는 각 파일의 파일명만 일치시킵니다. 하지만 --full-path 또는 -p 옵션을 사용하면 전체 경로(full path)를 기준으로 일치시킬 수 있습니다.
> fd -u -p -g '**/.git/config'
> fd -p '.*/lesson-\d+/[a-z]+.(jpg|png)'
명령어 실행 (Command execution)
단순히 검색 결과를 보여주는 것 외에, 종종 그 결과로 무언가를 수행하고 싶을 때가 있습니다. fd는 검색 결과 각각에 대해 외부 명령어를 실행하는 두 가지 방법을 제공합니다:
-x/--exec 옵션은 검색 결과 각각에 대해 외부 명령어를 실행합니다 (병렬로).
-X/--exec-batch 옵션은 모든 검색 결과를 *인수(argument)*로 사용하여 외부 명령어를 한 번 실행합니다.
예시 (Examples)
모든 zip 아카이브를 재귀적으로 찾아 압축을 해제하기:
만약 file1.zip과 backup/file2.zip처럼 두 개의 파일이 있다면, 이는 unzip file1.zip과 unzip backup/file2.zip을 실행합니다. 이 두 unzip 프로세스는 병렬로 실행됩니다 (파일을 충분히 빠르게 찾았다는 가정 하에).
모든 *.h 및 *.cpp 파일을 찾아 clang-format -i로 제자리에서 자동 포맷합니다:
fd -e h -e cpp -x clang-format -i
여기서 clang-format의 -i 옵션은 별도의 인수로 전달될 수 있다는 점에 주목하십시오. 이것이 바로 우리가 -x 옵션을 가장 마지막에 배치한 이유입니다.
-x 뒤의 모든 위치 인수는 fd 자체에 속하는 것이 아니라 명령어 템플릿에 속합니다. 패턴이나 검색 경로를 함께 전달하고 싶다면, -x를 가장 마지막에 두십시오:
모든 test_*.py 파일을 찾아 좋아하는 에디터에서 엽니다:
여기서 대문자 -X를 사용하면 단일 vim 인스턴스를 열게 됩니다. 만약 두 개의 파일, 즉 test_basic.py와 lib/test_advanced.py가 있다면, 이는 vim test_basic.py lib/test_advanced.py를 실행합니다.
파일 권한, 소유자, 파일 크기 등의 세부 정보를 보려면 각 결과에 대해 ls를 실행하도록 fd에 지시할 수 있습니다:
fd … -X ls -lhd --color=always
이 패턴은 매우 유용하여 fd가 단축키를 제공합니다. -l/--list-details 옵션을 사용하여 이 방식으로 ls를 실행할 수 있습니다: fd … -l.
-X 옵션은 또한 fd를 특정 클래스의 파일(예: 모든 C++ 소스 파일) 내에서 검색하는 ripgrep (rg)와 결합할 때 유용합니다:
fd -e cpp -e cxx -e h -e hpp -X rg 'std::cout'
모든 *.jpg 파일을 *.png 파일로 변환합니다:
fd -e jpg -x convert {} {.}.png
여기서 {}는 검색 결과에 대한 플레이스홀더(placeholder)입니다. {.}도 마찬가지로, 파일 확장자만 제외된 형태입니다.
플레이스홀더 구문에 대한 자세한 내용은 아래를 참조하십시오.
-x를 사용하여 병렬 스레드에서 실행되는 명령어의 터미널 출력은 섞이거나 손상되지 않으므로, fd -x는 많은 파일에 걸쳐 실행되는 작업을 간략하게 병렬화하는 데 사용될 수 있습니다. 한 가지 예로 디렉토리 내 개별 파일 각각의 체크섬(checksum)을 계산하는 것이 있습니다.
fd -tf -x md5sum > file_checksums.txt
플레이스홀더 구문
-x 및 -X 옵션은 단일 문자열 대신 일련의 인수로 *명령어 템플릿 (command template)*을 받습니다. 명령어 템플릿 뒤에 fd에 추가적인 옵션을 추가하려면, 이를 \;로 종료할 수 있습니다.
예를 들어, fd -x echo \; pattern path는 pattern path를 echo에 전달하는 대신 fd의 인수로 취급합니다. 실제로는 fd pattern path -x echo와 같이 작성하는 것이 더 명확한 경우가 많습니다.
명령어 생성 구문은 GNU Parallel의 구문과 유사합니다:
{}: 검색 결과 경로(documents/images/party.jpg)로 대체될 플레이스홀더 토큰입니다.
{.}: {}와 같지만 파일 확장자 없이 사용됩니다 (documents/images/party).
{/}: 검색 결과의 기본 이름(basename)으로 대체될 플레이스홀더입니다 (party.jpg).
{//}: 발견된 경로의 상위 디렉토리(documents/images)를 나타냅니다.
{/.}: 확장자가 제거된 기본 이름(party)을 나타냅니다.
플레이스홀더를 포함하지 않으면, fd는 자동으로 끝에 {}를 추가합니다.
병렬 실행 대 직렬 실행 (Parallel vs. serial execution)
-x/--exec의 경우, -j/--threads 옵션을 사용하여 병렬 작업(parallel jobs) 수를 제어할 수 있습니다. 직렬 실행을 하려면 --threads=1을 사용하십시오.
특정 파일 또는 디렉토리 제외 (Excluding specific files or directories)
때로는 특정 하위 디렉토리의 검색 결과만 무시하고 싶을 때가 있습니다. 예를 들어, 모든 숨김 파일 및 디렉토리(-H)를 검색하되, .git 디렉토리에서 발견된 일치 항목은 모두 제외하고 싶을 수 있습니다. 이때는 -E (또는 --exclude) 옵션을 사용할 수 있습니다. 이 옵션은 임의의 glob 패턴(glob pattern)을 인수로 받습니다:
또한 이를 사용하여 마운트된 디렉토리를 건너뛸 수도 있습니다:
> fd -E /mnt/external-drive …
또는 특정 파일 유형을 건너뛰기 위해 사용할 수 있습니다:
이러한 제외 패턴들을 영구적으로 만들려면, .fdignore 파일을 생성할 수 있습니다. 이 파일은 .gitignore 파일처럼 작동하지만, fd에만 특화되어 있습니다. 예를 들면 다음과 같습니다:
> cat ~/.fdignore
/mnt/external-drive
*.bak
[!NOTE]
fd는 또한 rg나 ag와 같은 다른 프로그램에서 사용되는 .ignore 파일도 지원합니다.
만약 fd가 이 패턴들을 전역적으로 무시하도록 하려면, fd의 전역 무시 파일에 넣을 수 있습니다.
이 파일은 일반적으로 macOS 또는 Linux에서는 ~/.config/fd/ignore에, Windows에서는 %APPDATA%\fd\ignore에 위치합니다.
--hidden 옵션을 사용할 경우 .git 디렉토리와 그 내용이 출력에 포함되는 것을 방지하기 위해 fd/ignore 파일에 .git/을 포함하는 것이 좋습니다.
파일 삭제
fd를 사용하여 검색 패턴과 일치하는 모든 파일 및 디렉토리를 제거할 수 있습니다.
파일만 제거하려면 --exec-batch/-X 옵션을 사용하여 rm을 호출할 수 있습니다. 예를 들어, 모든 .DS_Store 파일을 재귀적으로 제거하려면 다음 명령어를 실행합니다:
> fd -H '^\.DS_Store$' -tf -X rm
확신이 서지 않는다면, 항상 -X rm 없이 먼저 fd를 호출하십시오. 또는 rm의 "대화형(interactive)" 옵션을 사용하십시오:
> fd -H '^\.DS_Store$' -tf -X rm -i
특정 클래스의 디렉토리도 제거하려면 동일한 기술을 사용할 수 있습니다. 디렉토리를 제거하려면 rm의 --recursive/-r 플래그를 사용해야 합니다.
[!NOTE]
fd … -X rm -r을 사용하는 경우 경쟁 조건(race conditions)이 발생할 수 있는 시나리오가 있습니다. 예를 들어, 경로가 …/foo/bar/foo/...와 같고 이름이 foo인 모든 디렉토리를 제거하려 할 때, 바깥쪽의 foo 디렉토리가 먼저 제거되어 rm 호출에서 (무해한) "'foo/bar/foo': No such file or directory" 오류가 발생할 수 있습니다.
명령줄 옵션
이것은 fd -h의 출력입니다. 전체 명령줄 옵션 세트를 보려면, 훨씬 더 상세한 도움말 텍스트가 포함된 fd --help를 사용하십시오.
Usage: fd [OPTIONS] [pattern [path]...]
Arguments:
...
옵션은 패턴 및/또는 경로 뒤에 지정될 수도 있다는 점에 유의하십시오.
벤치마크
내 홈 폴더에서 [0-9].jpg로 끝나는 파일을 검색해 보겠습니다. 여기에는 약 750,000개의 하위 디렉터리와 약 4백만 개의 파일이 포함되어 있습니다. 평균 및 통계 분석을 위해 hyperfine를 사용하고 있습니다. 다음 벤치마크는 "웜(warm)"/사전 채워진 디스크 캐시로 수행되었으며 ("콜드(cold)" 디스크 캐시의 결과도 동일한 추세를 보여줍니다).
먼저 find로 시작해 보겠습니다:
Benchmark 1: find ~ -iregex '.*[0-9]\.jpg'$
Time (mean ± σ): 19.922 s ± 0.109 s
Range (min … max): 19.765 s … 20.065 s
find는 정규 표현식(regular-expression) 검색을 수행할 필요가 없다면 훨씬 빠릅니다:
Benchmark 2: find ~ -iname '*[0-9].jpg'$
Time (mean ± σ): 11.226 s ± 0.104 s
Range (min … max): 11.119 s … 11.466 s