터미널에서 API 응답이나 로그의 JSON을 다루다 보면 grep/sed는 중첩된 배열/객체 구조 앞에서 금방 한계에 부딪힌다. 이 프로젝트의 output/post_index.json도 400개 넘는 포스트 정보가 { "159": {...}, "348": {...} } 형태로 중첩돼 있어, “draft 상태인 글만” 같은 질의를 텍스트 매칭으로 처리하기 어렵다. jq는 .field 접근과 select() 필터링, map() 변환을 파이프(|)로 이어붙여 이런 질의를 처리하는 커맨드라인 JSON 프로세서다. 이 글에서는 jq의 핵심 필터 문법을 정리하고, 실제 이 프로젝트의 JSON 파일을 대상으로 직접 실행한 결과를 담는다.
jq 필터 문법
자주 쓰는 필터와 옵션만 정리하면 아래 표 정도로 충분하다. 나머지는 이 필터들을 파이프로 조합해서 해결된다.
| 문법 | 설명 | 예시 |
|---|---|---|
.field | 객체에서 특정 키 값 추출 | jq '.last_synced' |
.a.b / .["key"] | 중첩 객체 접근 (키에 특수문자·숫자만 있으면 대괄호 표기) | jq '.posts["159"]' |
.[] | 배열/객체의 모든 값을 스트림으로 펼쳐서 출력 | jq '.posts[]' |
select(cond) | 조건이 참인 항목만 통과시키는 필터 | select(.status == "draft") |
map(expr) | 배열의 각 항목을 expr로 변환한 새 배열 생성 | map(.title) |
| | 파이프. 앞 필터의 출력을 다음 필터 입력으로 연결 | .posts | to_entries | map(.value.title) |
to_entries / from_entries | 객체 ↔ {key, value} 배열 상호 변환 | .posts | to_entries |
length | 배열/객체/문자열의 길이(개수) | .posts | length |
sort_by(expr) / group_by(expr) | expr 기준 정렬 / 그룹핑 | group_by(.status) |
-r | 결과를 따옴표 없는 raw 텍스트로 출력 (문자열일 때) | jq -r '.title' |
-c | 결과를 줄바꿈 없이 한 줄로 압축 출력 | jq -c '.' |
--arg name value | 셸 변수를 jq 필터 안에서 $name으로 사용 | jq --arg kw "Python" '...' |
실전 코드
아래 예시는 전부 이 프로젝트의 실제 파일 output/post_index.json(post_index.py가 관리하는 WordPress 글 로컬 인덱스)을 대상으로 그대로 실행한 결과다.
필드 접근과 순회
$ jq '.last_synced' output/post_index.json
$ jq '.posts["159"]' output/post_index.json"2026-07-21T13:04:45"
{
"title": "candidate",
"status": "draft",
"modified": "2023-07-24T17:24:33"
}
.posts는 키가 post ID인 객체다. .posts[]로 값만 스트림으로 펼친 뒤 -r로 raw 문자열을 뽑으면 제목 목록을 바로 얻는다.
$ jq -r '.posts[] | .title' output/post_index.json | head -3candidate
Linux Kernel 에서 System call 처리 1
파이썬 기초 문법
조건 필터링: select()
status가 draft인 글만 골라 개수를 세고, -r로 제목만 뽑아본다.
$ jq '[.posts[] | select(.status == "draft")] | length' output/post_index.json
$ jq -r '.posts[] | select(.status == "draft") | .title' output/post_index.json | head -567
candidate
Linux Kernel 에서 System call 처리 1
Ubuntu 서버에서 네트워크 구성
Ubuntu 서버에서 DNS 설정하기
리눅스에서 Visual Studio Code 사용
변환: map()과 to_entries
.posts는 객체라서 map()을 바로 쓸 수 없다. to_entries로 {key, value} 배열로 바꾼 뒤 map()으로 원하는 형태로 재구성한다.
$ jq '[.posts | to_entries[] | {id: .key, title: .value.title}] | .[0:3]' output/post_index.json[
{
"id": "159",
"title": "candidate"
},
{
"id": "348",
"title": "Linux Kernel 에서 System call 처리 1"
},
{
"id": "529",
"title": "파이썬 기초 문법"
}
]
변수 주입: –arg
셸 변수나 스크립트 인자를 jq 필터 안에 안전하게 넣고 싶을 때 --arg를 쓴다. 필터 문자열 안에 값을 직접 이어붙이면 따옴표·특수문자 이스케이프 문제가 생기기 쉬운데, --arg는 $이름 변수로 주입해주므로 그런 문제가 없다.
$ jq --arg kw "Python" '[.posts[] | select(.title | contains($kw))] | length' output/post_index.json20
파이프라인 조합과 집계
필터를 |로 계속 이어붙이면 “draft 중 최근 수정된 5건만, id/수정일/제목만” 같은 다단계 질의도 한 줄로 처리된다.
$ jq -r '.posts | to_entries
| map(select(.value.status == "draft"))
| sort_by(.value.modified) | reverse
| .[0:5][]
| "\(.key)\t\(.value.modified)\t\(.value.title)"' output/post_index.json3571 2026-07-12T15:36:29 F2FS 파일시스템 압축 적용에 대한 고찰
3420 2026-07-05T07:25:12 성심당 줄 길 땐? 에디터가 직접 먹은 대전 추천 빵집 5곳
742 2026-07-01T17:04:47 클라우드웨이즈로 워드프레스 설치 및 도메인 설정 방법
1825 2026-07-01T17:00:00 Ubuntu 22.04 LXC 컨테이너 사용하기
2747 2026-07-01T16:59:37 Ubuntu 25.04 Desktop 설치 가이드
group_by와 sort_by를 조합하면 상태별 개수 같은 간단한 집계도 바로 나온다.
$ jq '[.posts[].status] | group_by(.) | map({status: .[0], count: length}) | sort_by(-.count)' output/post_index.json[
{
"status": "publish",
"count": 337
},
{
"status": "draft",
"count": 67
},
{
"status": "trash",
"count": 3
}
]
주의사항
- 기본 출력은 JSON 문자열:
-r없이.title만 실행하면 값이"candidate"처럼 따옴표로 감싸진 채 나온다. 셸 스크립트에서 변수에 담아 쓰려면-r이 필수다. - 필드가 없는 경우 에러:
.posts.foo처럼 존재하지 않는 키를 파고들면 배열/객체가 아닌 값에서 접근 시 에러가 날 수 있다. 존재 여부가 불확실하면.foo?처럼 물음표를 붙이거나// "기본값"으로 폴백을 준다. - 필터 안 따옴표 충돌: 필터를 작은따옴표(
'...')로 감싸는 게 일반적인데, 필터 내부에서 문자열 리터럴에 큰따옴표를 쓰면("Python") 셸 인용과 안 겹친다. 값이 외부(셸 변수)에서 온다면 문자열을 직접 이어붙이지 말고--arg로 주입하는 편이 안전하다. - root 권한이 없는 환경:
sudo apt-get install -y jq가 안 되면 jq 공식 릴리스의 정적 바이너리를 받아~/.local/bin같은 PATH 안 디렉터리에 두고 실행 권한만 주면 된다. - 대용량 파일: jq는 기본적으로 전체 JSON을 메모리에 로드한다. 수 GB짜리 로그라면
--stream옵션이나 애초에 파일을 줄 단위(JSON Lines)로 분리하는 방식을 고려한다.
마무리
jq는 .field, .[], select(), map() 네 가지와 파이프만 익혀도 실무 JSON 질의 대부분을 처리할 수 있다. 여기에 raw 출력용 -r, 외부 값을 안전하게 넣는 --arg까지 더하면 curl ... | jq ...처럼 API 응답을 바로 셸 파이프라인에 태우는 패턴도 무리 없이 짤 수 있다.