이번 편의 결과물: todo search <패턴>이 정규식으로 할 일 제목을 검색해 일치하는 항목만 출력하고, 일치한 부분을 대괄호로 표시합니다. · 다루는 개념: 정규식 리터럴/플래그, test/match/replace
이 편에서 만드는 파일
todo-cli/
└── src/
└── commands/
└── search.js (+)src/index.js에도 search 명령 분기가 하나 추가됩니다.
개념 정리
정규식 리터럴과 플래그
정규식(regular expression)은 문자열에서 특정 패턴을 찾는 문법입니다. 슬래시(/) 사이에 패턴을 쓰고, 뒤에 플래그를 붙입니다.
| 표기 | 의미 |
|---|---|
/제출/ | ”제출”이라는 문자열을 포함하는지 검사 |
/제출/i | 대소문자 구분 없이 검사(i = ignore case) |
/제출/g | 문자열 전체에서 모든 일치를 찾음(g = global) |
new RegExp(문자열, 플래그) | 문자열로 정규식을 동적으로 생성 |
검색어를 사용자가 명령행에서 입력하는 todo-cli는 패턴을 코드에 미리 적어둘 수 없으므로, 리터럴(/.../) 대신 new RegExp() 생성자로 동적 정규식을 만듭니다.
정규식 메서드 3가지
| 메서드 | 호출 주체 | 반환값 | 용도 |
|---|---|---|---|
regex.test(문자열) | 정규식 | boolean | 일치 여부만 확인 |
문자열.match(regex) | 문자열 | 일치 정보 배열 또는 null | 일치한 내용 자체를 확인 |
문자열.replace(regex, 대체값) | 문자열 | 치환된 새 문자열 | 일치한 부분을 바꾸거나 표시 |
replace의 두 번째 인자로 함수를 넘기면, 일치한 문자열을 인자로 받아 원하는 형태로 가공할 수 있습니다. todo-cli 검색은 test로 일치 항목을 걸러내고, replace로 일치한 부분을 대괄호로 감싸 표시합니다.
잘못된 패턴 처리
사용자가 [처럼 정규식 문법으로는 불완전한 문자열을 입력하면 new RegExp()가 SyntaxError를 던집니다. try/catch로 감싸 사용자에게 읽을 수 있는 메시지를 보여줘야 합니다. 에러 처리 자체는 14편에서 더 다루고, 여기서는 최소한의 방어만 합니다.
실습
1. src/commands/search.js 파일 만들기
src/commands/ 폴더에 search.js를 새로 만듭니다.
2. 코드 작성
// src/commands/search.js
export function runSearch(store, positional) {
const [pattern] = positional;
if (!pattern) {
console.log('검색어를 입력하세요. 예: node src/index.js search 제출');
return;
}
let regex;
try {
regex = new RegExp(pattern, 'i');
} catch {
console.log('올바르지 않은 정규식입니다:', pattern);
return;
}
const matched = store.toArray().filter((todo) => regex.test(todo.title));
if (matched.length === 0) {
console.log('일치하는 할 일이 없습니다.');
return;
}
matched.forEach((todo) => {
const highlighted = todo.title.replace(regex, (match) => `[${match}]`);
console.log(`#${todo.id} ${highlighted}`);
});
}regex.test(todo.title)은 제목에 패턴이 포함되어 있는지만 확인하므로 filter의 조건 함수로 바로 쓸 수 있습니다. title.replace(regex, 콜백)은 콜백이 돌려준 대괄호 문자열로 실제로 일치한 부분(대소문자가 섞여 있어도 원래 표기 그대로)을 대괄호로 감쌉니다. i 플래그를 고정으로 붙여 대소문자를 구분하지 않고 검색합니다.
src/index.js에 search 명령 분기를 추가합니다.
// src/index.js
import { parseArgs } from './cli/parseArgs.js';
import { TodoStore } from './models/TodoStore.js';
import { loadTodos, saveTodos } from './storage/fileStore.js';
import { runAdd } from './commands/add.js';
import { runList } from './commands/list.js';
import { runTags } from './commands/tags.js';
import { runSearch } from './commands/search.js';
const argv = process.argv.slice(2);
const { command, positional, flags } = parseArgs(argv);
const rawTodos = await loadTodos();
const store = TodoStore.fromArray(rawTodos);
if (command === 'add') {
await runAdd(store, positional, flags);
await saveTodos(store.toArray().map((todo) => todo.toJSON()));
} else if (command === 'list') {
runList(store, flags);
} else if (command === 'tags') {
runTags(store);
} else if (command === 'search') {
runSearch(store, positional);
} else {
console.log('알 수 없는 명령입니다:', command);
}3. 실행
node src/index.js search clinode src/index.js search 카드확인
search cli결과(제목에 “cli”가 들어간 항목, 대소문자 구분 없이 “todo-cli”의 “cli”와 일치):
#3 todo-[cli]에 검색 명령 추가search 카드결과(제목에 “카드”가 들어간 항목):
#2 포트폴리오 프로젝트 [카드] 6개 채우기search만 입력하고 검색어를 생략하면 “검색어를 입력하세요. 예: node src/index.js search 제출”가 출력됩니다.search [처럼 정규식 문법이 불완전한 값을 넣으면 “올바르지 않은 정규식입니다: [“이 출력되고 프로그램이 죽지 않습니다.
직접 해보기
search명령에--exact플래그가 있을 때만 대소문자를 구분하도록(i플래그를 빼도록)runSearch를 고쳐봅니다.문자열.match(regex)를 콘솔에서 직접 실행해 일치한 부분과 인덱스 정보가 담긴 배열이 반환되는 것을 확인합니다.
정답 보기
// src/commands/search.js
export function runSearch(store, positional, flags = {}) {
const [pattern] = positional;
if (!pattern) {
console.log('검색어를 입력하세요. 예: node src/index.js search 제출');
return;
}
let regex;
try {
regex = new RegExp(pattern, flags.exact ? '' : 'i');
} catch {
console.log('올바르지 않은 정규식입니다:', pattern);
return;
}
const matched = store.toArray().filter((todo) => regex.test(todo.title));
if (matched.length === 0) {
console.log('일치하는 할 일이 없습니다.');
return;
}
matched.forEach((todo) => {
const highlighted = todo.title.replace(regex, (match) => `[${match}]`);
console.log(`#${todo.id} ${highlighted}`);
});
}index.js의 호출부도 runSearch(store, positional, flags)로 세 번째 인자를 넘기도록 함께 고쳐야 합니다.
자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
| 대소문자가 다르면 검색이 안 됨 | i 플래그를 빼먹음 | new RegExp(pattern, 'i')처럼 플래그를 붙인다 |
| 특수문자가 든 검색어에서 오류 발생 | (, [ 같은 정규식 전용 문자를 그대로 패턴으로 씀 | try/catch로 감싸 사용자에게 안내 메시지를 보여준다 |
replace로 표시가 안 됨 | 정규식에 g 플래그가 없어 첫 일치만 바뀜(여러 번 나오는 경우) | 모든 일치를 바꾸려면 g 플래그를 추가한다 |
확인 문제
[${match}])에서 콜백 함수의 match 매개변수가 받는 값은?