알림 페이로드나 REST 응답에서 값 몇 개를 뽑아 다음 명령에 넘기는 일은 grep 과 sed 로 하면 중첩 구조와 이스케이프에서 곧바로 깨진다. jq 는 JSON 을 구조로 읽으므로 이런 작업에 맞는다.
# RHEL 계열
sudo dnf install -y jq
# Ubuntu
sudo apt install -y jq
배열로 된 알림 목록에서 특정 조건을 만족하는 항목의 두 필드를 꺼내는 예다.
jq -r '
.[]
| select(
.body.alert.attributes.BAD_TEST_RESULTS[0] == "1"
and (.body.alert.attributes.HEALTH_TEST_RESULTS[]?.severity == "CRITICAL")
)
| "\(.body.alert.attributes.ALERT_SUMMARY[0])\t\(.body.alert.source)"
' alerts.json
-r 은 문자열의 큰따옴표를 벗겨 낸다. 값을 셸 변수에 담을 때 거의 항상 필요하다..[] 는 배열의 각 요소를 순회한다.select(조건) 은 조건을 만족하는 것만 남긴다..field[]? 의 ? 는 그 경로가 없거나 배열이 아닐 때 오류 대신 건너뛴다. 스키마가 들쭉날쭉한 실제 데이터에서는 이것이 있어야 중간에 죽지 않는다."\(...)" 는 문자열 보간이다.값이 하나면 그대로 명령 치환으로 받는다.
TOKEN="$(jq -r '.access_token' /tmp/auth.json)"
여러 줄을 짝지어 읽을 때는 구분자를 명시하는 편이 안전하다. 값 안에 공백이 있어도 깨지지 않도록 탭으로 나누고 IFS 를 지정한다.
jq -r '.[] | "\(.name)\t\(.url)"' items.json |
while IFS=$'\t' read -r NAME URL; do
echo "name=${NAME} url=${URL}"
done
파이프로 넘긴 while 루프는 서브셸에서 돈다. 루프 안에서 설정한 변수는 루프를 벗어나면 사라진다. 값을 밖에서 써야 하면 프로세스 치환을 쓴다.
while IFS=$'\t' read -r NAME URL; do
LAST_NAME="${NAME}"
done < <(jq -r '.[] | "\(.name)\t\(.url)"' items.json)
echo "${LAST_NAME}"
배열로 통째로 받으려면 mapfile 을 쓴다.
mapfile -t NAMES < <(jq -r '.[].name' items.json)
echo "${#NAMES[@]}"
#!/usr/bin/env bash
set -euo pipefail
TOKEN="${API_TOKEN:?API_TOKEN is required}"
URL="https://api.example.com/v1/events"
PAYLOAD="$(jq -c '
{
summary: .body.alert.attributes.ALERT_SUMMARY[0],
source: .body.alert.source
}' alerts.json)"
curl -fsS -X POST "${URL}" \
-H "Authorization: Bearer ${TOKEN}" \
-H 'Content-Type: application/json' \
--data-binary "${PAYLOAD}"
-c 는 한 줄로 압축해 출력한다. 전송용 페이로드는 -r 이 아니라 -c 를 쓴다. -r 로 벗겨 내면 JSON 이 아니게 된다.
JSON 을 문자열 조합으로 만들지 않는다. 값에 큰따옴표나 줄바꿈이 있으면 곧바로 깨진다. 셸 변수에서 JSON 을 만들 때도 jq 에 맡긴다.
jq -n --arg name "${NAME}" --arg url "${URL}" '{name: $name, url: $url}'
토큰 값은 스크립트에 적지 않고 환경 변수나 권한을 제한한 파일에서 읽는다.