jq — JSON 쿼리 및 변환
개요
jq는 JSON을 쿼리하고 변형하는 표준 CLI 도구입니다. 이 스킬은 깊게 중첩된 데이터 필터링, 구조 변환, 값 집계, 그리고 jq를 셸 파이프라인에 구성하는 실용적이고 전문가 수준의 사용법을 다룹니다. 모든 예제는 실제 워크플로우에 복사-붙여넣기 가능합니다.
이 스킬을 사용해야 하는 경우
- API, CLI 도구(AWS, GitHub, kubectl, docker) 또는 로그 파일에서 JSON 출력을 파싱할 때
- JSON 구조를 변환(키 이름 변경, 배열 평탄화, 레코드 그룹화)할 때
- 사용자가 bash 스크립트나 원라인 명령어 안에
jq가 필요할 때 - 복잡한
jq표현식의 동작을 설명해야 할 때
작동 방식
jq는 필터 표현식을 받아 JSON 입력에 적용합니다. 필터는 파이프(|)로 구성되며, jq는 배열, 객체, 문자열, 숫자, 부울, null을 기본적으로 처리합니다.
기본 선택
# 필드 추출
echo '{"name":"alice","age":30}' | jq '.name'
# "alice"
# 중첩 접근
echo '{"user":{"email":"[email protected]"}}' | jq '.user.email'
# 배열 인덱스
echo '[10, 20, 30]' | jq '.[1]'
# 20
# 배열 슬라이스
echo '[1,2,3,4,5]' | jq '.[2:4]'
# [3, 4]
# 모든 배열 요소
echo '[{"id":1},{"id":2}]' | jq '.[]'
select로 필터링
# 일치하는 요소만 유지
echo '[{"role":"admin"},{"role":"user"},{"role":"admin"}]' \
| jq '[.[] | select(.role == "admin")]'
# 숫자 비교
curl -s https://api.github.com/repos/owner/repo/issues \
| jq '[.[] | select(.comments > 5)]'
# 필드 존재 및 null이 아님 확인
jq '[.[] | select(.email != null)]'
# 조건 결합
jq '[.[] | select(.active == true and .score >= 80)]'
매핑 및 변환
# 모든 배열 요소에서 필드 추출
echo '[{"name":"alice","age":30},{"name":"bob","age":25}]' \
| jq '[.[] | .name]'
# ["alice", "bob"]
# 단축: map()
jq 'map(.name)'
# 요소당 새 객체 생성
jq '[.[] | {user: .name, years: .age}]'
# 계산된 필드 추가
jq '[.[] | . + {senior: (.age > 28)}]'
# 키 이름 변경
jq '[.[] | {username: .name, email_address: .email}]'
집계 및 리듀스
# 모든 값의 합
echo '[1, 2, 3, 4, 5]' | jq 'add'
# 15
# 객체 전체에서 특정 필드 합계
jq '[.[].price] | add'
# 요소 개수 세기
jq 'length'
# 최대 / 최소
jq 'max_by(.score)'
jq 'min_by(.created_at)'
# reduce: 사용자 정의 누산기
echo '[1,2,3,4,5]' | jq 'reduce .[] as $x (0; . + $x)'
# 15
# 필드별 그룹화
jq 'group_by(.department)'
# 그룹별 개수
jq 'group_by(.status) | map({status: .[0].status, count: length})'
문자열 보간 및 서식 지정
# 문자열 보간
jq -r '.[] | "\(.name) is \(.age) years old"'
# CSV 형식 (헤더 없음)
jq -r '.[] | [.name, .age, .email] | @csv'
# TSV 형식
jq -r '.[] | [.name, .score] | @tsv'
# 값 URL 인코딩
jq -r '.query | @uri'
# Base64 인코딩
jq -r '.data | @base64'
키와 경로 다루기
# 모든 최상위 키 나열
jq 'keys'
# 키 존재 여부 확인
jq 'has("email")'
# 키 삭제
jq 'del(.password)'
# 모든 요소에서 중첩 키 삭제
jq '[.[] | del(.internal_id, .raw_payload)]'
# 재귀적 하강: 트리 어디서든 특정 키의 모든 값 찾기
jq '.. | .id? // empty'
# 모든 리프 경로 가져오기
jq '[paths(scalars)]'
조건문 및 오류 처리
# if-then-else
jq 'if .score >= 90 then "A" elif .score >= 80 then "B" else "C" end'
# 대체 연산자: null 또는 false인 경우 대체값 사용
jq '.nickname // .name'
# try-catch: 중단 대신 오류 건너뛰기
jq '[.[] | try .nested.value catch null]'
# // empty로 null 출력 억제
jq '.[] | .optional_field // empty'
실용적인 셸 통합
# 파일에서 읽기
jq '.users' data.json
# 추가 파이핑을 위한 간결한 출력 (공백 없음)
jq -c '.[]' records.json | while IFS= read -r record; do
echo "처리 중: $record"
done
# 셸 변수를 jq에 전달
STATUS="active"
jq --arg s "$STATUS" '[.[] | select(.status == $s)]'
# 숫자 전달
jq --argjson threshold 42 '[.[] | select(.value > $threshold)]'
# 여러 JSON 라인을 배열로 슬러프
jq -s '.' records.ndjson
# 여러 파일: 모두 하나의 배열로 슬러프
jq -s 'add' file1.json file2.json
# 명령어에서 null-safe 파이프라인
kubectl get pods -o json | jq '.items[] | {name: .metadata.name, status: .status.phase}'
# GitHub CLI: PR 번호 추출
gh pr list --json number,title | jq -r '.[] | "\(.number)\t\(.title)"'
# AWS CLI: 실행 중인 인스턴스 ID 나열
aws ec2 describe-instances \
| jq -r '.Reservations[].Instances[] | select(.State.Name=="running") | .InstanceId'
# Docker: 컨테이너 이름과 이미지 표시
docker inspect $(docker ps -q) | jq -r '.[] | "\(.Name)\t\(.Config.Image)"'
고급 패턴
# 객체의 배열을 객체의 배열로 전치
# 입력: {"names":["a","b"],"scores":[10,20]}
jq '[.names, .scores] | transpose | map({name: .[0], score: .[1]})'
# 한 수준 평탄화
jq 'flatten(1)'
# 필드별 중복 제거
jq 'unique_by(.email)'
# 정렬, 중복 제거 및 재인덱싱
jq '[.[] | .name] | unique | sort'
# walk: 모든 노드에 재귀적으로 변환 적용
jq 'walk(if type == "string" then ascii_downcase else . end)'
# env: jq 내에서 환경 변수 읽기
export API_KEY=secret
jq -n 'env.API_KEY'
모범 사례
jq결과를 셸 변수나 다른 명령어에 전달할 때는 항상-r(원시 출력)을 사용하여 JSON 문자열 따옴표를 제거하십시오--arg/--argjson을 사용하여 셸 변수를 안전하게 주입하십시오 — 필터 문자열에 셸 변수를 직접 보간하지 마십시오- 가독성을 위해
[.[] | f]보다는map(f)를 선호하십시오 - 개행으로 구분된 JSON 파이프라인에는
-c(간결)를 사용하고, 사람이 읽기 쉬운 디버깅에는 생략하십시오 - 스크립트에 포함하기 전에
jq -n과 리터럴 입력으로 필터를 대화형으로 테스트하십시오 - 원하지 않는 요소를
null로 필터링하는 대신empty를 사용하여 제거하십시오
보안 및 안전 참고 사항
jq는 설계상 읽기 전용입니다 — 파일을 쓰거나 명령어를 실행할 수 없습니다- 신뢰할 수 없는 JSON 필드 값을 셸 명령어에 직접 포함하지 마십시오; 항상 인용하거나
--arg를 사용하십시오
일반적인 함정
-
문제:
jq가 예상 값 대신null을 출력합니다 해결책: 키 이름에 오타가 있는지 확인하십시오;keys를 사용하여 실제 필드 이름을 검사하십시오. JSON은 대소문자를 구분합니다. -
문제: 숫자가 출력에서 문자열로 인용됩니다 해결책: 숫자 값을 주입할 때
--arg대신--argjson을 사용하십시오. -
문제: 터미널에서는 필터가 작동하지만 스크립트에서는 실패합니다 해결책: 필터 문자열이 셸에서 작은따옴표를 사용하여 변수 확장을 방지하는지 확인하십시오. 예:
jq '.field'가jq ".field"가 아닙니다. -
문제: 빈 배열에
add를 사용하면null이 반환됩니다 해결책:add // 0또는add // ""를 사용하여 기본 대체값을 제공하십시오. -
문제: 대용량 파일 스트리밍이 느립니다 해결책: 매우 큰 파일의 경우
jq --stream을 사용하거나jstream/gron으로 전환하십시오.
관련 스킬
@bash-pro— 강력한 셸 스크립트에서 jq 호출 래핑@bash-linux— 일반 셸 파이프라인 패턴@github-automation— GitHub CLI JSON 출력과 함께 jq 사용
제한 사항
- 이 스킬은 작업이 위에서 설명한 범위와 명확히 일치할 때만 사용하십시오.
- 출력물이 환경별 검증, 테스트 또는 전문가 검토를 대체할 수 있다고 간주하지 마십시오.
- 필수 입력, 권한, 안전 경계 또는 성공 기준이 누락된 경우 중단하고 명확히 설명을 요청하십시오.