Chương 04 — Search: grep, find, fzf
Tìm nội dung trong file với grep, tìm file trong cây thư mục với find,
và tăng tốc gấp 10 lần với bộ ba hiện đại ripgrep, fd, fzf. Đây là kỹ năng
phân biệt người dùng CLI biết qua loa với người thật sự nhanh.
1. grep cơ bản — tìm chuỗi trong file
grep (Global Regular Expression Print) đọc input dòng-theo-dòng và in ra những dòng khớp pattern. Cú pháp tổng quát:
$ grep [options] "pattern" file
Các cờ phải nhớ
$ grep "error" server.log # tìm "error" — phân biệt hoa/thường
$ grep -i "error" server.log # -i: case-insensitive (Error, ERROR, error)
$ grep -n "TODO" main.py # -n: in kèm số dòng
$ grep -c "error" server.log # -c: ĐẾM số dòng khớp (không in ra dòng)
$ grep -v "DEBUG" server.log # -v: INVERT — in dòng KHÔNG khớp
$ grep -r "console.log" src/ # -r: đệ quy qua mọi file trong src/
$ grep -w "log" src/main.js # -w: word boundary — không khớp "login", "logger"
Ghép cờ — rất phổ biến
$ grep -rin "todo" src/ # đệ quy + case-insensitive + số dòng
$ grep -rwn "id" . # từ "id" đứng riêng, không phải "uuid"/"width"
-v để loại bỏ noiseDEBUG? Lọc chúng ra trước:
grep -v "DEBUG" server.log | grep "user_id=42". Đây là use case kinh điển ở Chương 3 (pipes).
2. grep với regex
Pattern của grep mặc định là BRE (Basic Regular Expression). Để dùng cú pháp regex hiện đại, thêm -E (Extended) — tránh phải escape +, ?, |, {}.
Anchors & character class
$ grep "^ERROR" server.log # ^ : đầu dòng
$ grep "\.js$" filelist.txt # $ : cuối dòng — file kết thúc bằng .js
$ grep "[0-9]" data.txt # bất kỳ chữ số
$ grep "[A-Za-z]" data.txt # chữ cái
$ grep "[^0-9]" data.txt # [^...] : KHÔNG phải chữ số
Extended regex với -E
$ grep -E "error|warning|fatal" log # | : alternation (HOẶC)
$ grep -E "colou?r" doc.txt # ? : 0 hoặc 1 (color, colour)
$ grep -E "a+" input # + : 1 hoặc nhiều
$ grep -E "[0-9]{3}-[0-9]{4}" phones # {n} : đúng n lần — "555-1234"
Ví dụ thực tế — tìm email và IP
$ grep -Eo "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}" contacts.txt
alice@example.com
bob@dev.io
$ grep -Eo "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log
192.168.1.10
10.0.0.42
Cờ -o (only-matching): chỉ in ra phần KHỚP pattern, không in cả dòng. Rất hữu dụng khi muốn extract dữ liệu.
"..." hoặc '...'. Nếu không, shell sẽ expand các ký tự như *, $, ?
TRƯỚC KHI grep nhìn thấy chúng — kết quả thường sai bí ẩn.
3. grep nâng cao — context, filter, pipe
Context: in cả dòng xung quanh
$ grep -A 3 "error" server.log # -A 3: in 3 dòng SAU dòng khớp (After)
$ grep -B 2 "error" server.log # -B 2: in 2 dòng TRƯỚC (Before)
$ grep -C 3 "error" server.log # -C 3: cả trên 3 + dưới 3 (Context)
Lọc theo loại file
$ grep -r "TODO" . --include="*.js" # chỉ tìm trong .js
$ grep -r "TODO" . --exclude="*.min.js" # bỏ qua file đã minify
$ grep -r "TODO" . --exclude-dir=node_modules
Chỉ in TÊN FILE chứa pattern (-l)
$ grep -rl "console.log" src/
src/api/auth.js
src/utils/log.js
src/components/Button.js
Hữu dụng khi muốn pipe danh sách file sang lệnh khác (vd: mở trong editor, xoá, sửa hàng loạt).
Pipe nhiều grep liên tiếp — boolean AND
$ grep "user_id=42" server.log | grep "error" | grep -v "timeout"
# Dòng có user_id=42 VÀ có error VÀ KHÔNG có timeout
grep -rn "TODO\|FIXME\|XXX" src/ --exclude-dir=node_modules
Lệnh trên kết hợp -r (đệ quy), -n (số dòng), regex alternation, và loại bỏ node_modules.
Đây là lệnh bạn sẽ gõ hàng nghìn lần trong sự nghiệp coding.
4. find — tìm FILE theo thuộc tính
Khác với grep (tìm nội dung), find tìm file theo tên/loại/kích thước/thời gian. Cú pháp:
$ find <path> <tests> <actions>
Tìm theo tên
$ find . -name "*.js" # mọi file .js từ folder hiện tại trở xuống
$ find . -iname "readme*" # -iname: không phân biệt hoa/thường
$ find ~/Documents -name "*.pdf"
-namefind . -name *.txt KHÔNG có dấu nháy, shell expand *.txt thành danh sách file
ở folder hiện tại trước, rồi truyền vào find — kết quả sai hoàn toàn.
Phải là find . -name "*.txt" để find tự xử lý wildcard.
Tìm theo loại
$ find . -type f # f = file thường
$ find . -type d # d = directory
$ find . -type l # l = symbolic link
$ find . -type f -name "*.log" # kết hợp tests
Tìm theo kích thước & thời gian
$ find . -size +100M # lớn hơn 100MB (k/M/G)
$ find . -size -1k # nhỏ hơn 1KB
$ find . -mtime +30 # sửa lần cuối > 30 ngày trước
$ find . -mtime -7 # sửa trong 7 ngày qua
$ find . -newer reference.txt # sửa SAU file reference.txt
Use case kinh điển: file lớn cũ cần dọn
$ find ~/Downloads -type f -size +100M -mtime +30
/Users/you/Downloads/big-video.mp4
/Users/you/Downloads/dataset.zip
$ find ~/Downloads -type f -size +100M -mtime +30 -delete
# -delete: action xoá luôn (cẩn thận!)
5. find -exec & xargs
Tìm xong, ta thường muốn làm gì đó với danh sách file: xoá, copy, đổi quyền, mở, nén... Có 2 cách:
Cách 1 — -exec (built-in của find)
$ find . -name "*.tmp" -exec rm {} \;
# ^^ ^^
# | kết thúc -exec (escape ; cho shell)
# placeholder: chỗ này thay bằng tên file
$ find . -name "*.sh" -exec chmod +x {} \;
$ find . -name "*.log" -exec gzip {} \;
Cách 2 — pipe sang xargs
$ find . -name "*.tmp" | xargs rm
$ find . -name "*.log" | xargs gzip
xargs đọc input từ stdin (mỗi dòng = 1 argument) và đưa vào lệnh ở phía sau. Thường nhanh hơn -exec ... \; vì gọi lệnh 1 lần với nhiều args, thay vì gọi lần lượt từng file.
| Cách | Số lần gọi lệnh | Tốc độ |
|---|---|---|
-exec ... {} \; | 1 lần / file | Chậm |
-exec ... {} + | 1 lần / nhiều file (gom batch) | Nhanh |
| xargs ... | 1 lần / nhiều file | Nhanh |
-print0 + xargs -0Mặc định xargs tách theo khoảng trắng — nên file tên "my file.txt" sẽ bị tách thành 2 đối số!
Hậu quả: lệnh chạy sai, có khi xoá nhầm.
# SAI (nguy hiểm với tên file có space):
find . -name "*.tmp" | xargs rm
# ĐÚNG: dùng NUL byte làm separator
find . -name "*.tmp" -print0 | xargs -0 rm
-print0 in các tên cách nhau bằng byte NUL (\0); xargs -0 đọc theo cùng quy ước.
Đây là pattern an toàn chuẩn — hãy thuộc lòng.
Use case thực — tìm và sửa hàng loạt
# Đếm tổng số dòng trong mọi file .py
$ find . -name "*.py" | xargs wc -l
# Grep "import" trong mọi file .js (an toàn với tên file có space)
$ find . -name "*.js" -print0 | xargs -0 grep -l "import"
6. Bộ ba hiện đại: ripgrep, fd, fzf
grep và find là tổ tiên — vẫn có ở mọi máy Unix. Nhưng cộng đồng dev hiện đại hầu hết đã chuyển sang 3 công cụ viết bằng Rust, nhanh và dễ dùng hơn nhiều:
Cài đặt (macOS với Homebrew)
$ brew install ripgrep fd fzf
rg (ripgrep) — thay thế grep -r
$ rg "TODO" # mặc định đệ quy, có màu, có số dòng
$ rg "useState" -t js # -t js: chỉ trong file JavaScript
$ rg -i "error" --glob "!*.min.js" # bỏ qua file minified
| Tính năng | grep -r | rg |
|---|---|---|
| Đệ quy mặc định | Không (cần -r) | Có |
Bỏ qua .gitignore | Không | Có (mặc định) |
Bỏ qua node_modules, .git | Không | Có |
| Tốc độ trên repo lớn | Chậm | Nhanh hơn 5–20× |
| Output có màu | Cần --color | Mặc định |
fd — thay thế find -name
$ fd readme # tìm file/folder tên có "readme"
$ fd -e py # -e: extension — mọi file .py
$ fd -t d node_modules # -t d: chỉ folder, tên là node_modules
$ fd "\.log$" -x rm # -x: execute (giống find -exec)
Cú pháp fd ngắn hơn rất nhiều: không cần ., không cần -name, không cần quote. Mặc định bỏ qua .gitignore và file ẩn.
fzf — fuzzy finder interactive
$ fzf # mở UI tìm kiếm fuzzy file ở folder hiện tại
$ vim $(fzf) # mở vim với file chọn từ fzf
$ history | fzf # tìm lệnh cũ trong lịch sử
$ git branch | fzf | xargs git checkout # chọn branch để checkout
Sau khi cài, gõ $(brew --prefix)/opt/fzf/install để kích hoạt phím tắt:
- Ctrl-T: chèn file (chọn fuzzy) vào dòng lệnh đang gõ
- Ctrl-R: search history lệnh đã chạy (cực mạnh)
- Alt-C: cd vào folder chọn fuzzy
grep/find vì chúng có ở MỌI máy Unix (server, container, CI). Nhưng trên máy cá nhân hằng ngày, hãy dùng rg/fd/fzf — nhanh hơn, ít gõ hơn, ít sai hơn.
7. Thử ngay trong terminal giả lập
Simulator đã hỗ trợ các lệnh tìm kiếm cơ bản — thử các kịch bản:
- Liệt kê file:
ls - Tìm "TODO" trong file:
grep "TODO" notes.txt - Tìm đệ quy có số dòng:
grep -rn "error" . - Đảo ngược:
grep -v "DEBUG" log.txt - Tìm file theo tên:
find . -name "*.txt" - Tìm file lớn:
find . -size +1k - Pipe grep:
cat log.txt | grep "error" | grep -v "timeout"
8. Bài tập về nhà
Trên Terminal thật, làm tuần tự (xong báo lại tôi check):
- Tải 1 repo bất kỳ về máy (vd
git clone https://github.com/cli/cli). Tìm tất cả dòng chứaTODOtrong source code, in kèm số dòng. - Trong cùng repo, đếm tổng số dòng có
FIXMEbằnggrep -c. So sánh vớigrep | wc -l— kết quả có khác không? - Dùng
findliệt kê mọi file.mdtrong~/Documents(nếu chưa có thì tạo vài file test). - Tìm 5 file lớn nhất trong
~/Downloads:find ~/Downloads -type f -size +50M. Ghi lại danh sách trước khi quyết định xoá. - Pipe nâng cao (ôn Chương 3): tìm trong
/var/log/system.logcác dòng có "error", loại bỏ những dòng chứa "harmless", lấy 10 dòng cuối:grep "error" /var/log/system.log | grep -v "harmless" | tail -10. - find -exec: tạo folder
~/scriptsvới vài file.sh, sau đó cấp quyền chạy hàng loạt bằng 1 lệnh:find ~/scripts -name "*.sh" -exec chmod +x {} \;. - Cài
brew install ripgrep fd fzf. Chạyrg "TODO"trong cùng repo ở câu 1 — so sánh tốc độ vớigrep -rn "TODO" .(dùngtimetrước lệnh). - Thử Ctrl-R sau khi setup
fzf. Tìm lại 1 lệnh dài bạn đã gõ trước đó. - Bonus: viết 1 dòng tìm mọi file
.loglớn hơn 10MB đã không sửa trong 7 ngày, và xoá an toàn (gợi ý:find ... -print0 | xargs -0 rm).
9. Quiz — Kiểm tra kiến thức
10. Tổng kết
- ✅
greptìm nội dung — nhớ-i,-n,-r,-v,-w - ✅ Regex cơ bản: anchors
^$, character class, dùng-Echo extended - ✅ Context
-A/-B/-C, lọc file--include/--exclude - ✅
findtìm file theo name/type/size/mtime — luôn quote pattern - ✅
-exec ... {} \;hoặc pipexargs; an toàn với-print0+xargs -0 - ✅ Cài và quen
rg,fd,fzf— nhanh hơn, smart hơn - ✅ Use case quen tay: tìm TODO, dọn file rác lớn cũ, lọc log nhiễu
Chương 5 chúng ta sẽ học text processing — sed, awk, cut, sort, uniq: biến terminal thành công cụ ETL mini, xử lý CSV/log/text như chuyên gia data.