기초

기초 에서 jq

1개의 연습 문제

기초 소개

jq는 들어오는 JSON 데이터를 단일 표현식(_필터들의 파이프라인_으로 작성)에 통과시켜 원하는 변환 결과를 얻는 방식으로 동작해요.

jq 명령줄 옵션 간단히 살펴보기

jq 언어는 jq _프로그램_으로 구현되어 있어요. 이 프로그램은 입력이 어떻게 처리되고 출력이 어떻게 표시될지 제어하는 편리한 명령줄 옵션을 여러 개 제공해요.

아래 예제에서 이런 옵션들을 만나게 될 거예요:

  • -n 또는 --null-input

    보통 jq 프로그램에는 읽을 파일을 지정하거나, 입력으로 데이터를 보내요. --null-input 옵션을 사용하면 입력 없이 JSON 데이터를 생성할 수 있어요.

  • -c 또는 --compact-output

    jq는 기본적으로 출력을 보기 좋게 정렬해서 출력해요. 데이터가 보기 좋게 포맷되어 있으면 사람이 확인하기에 매우 유용하죠. 하지만 기계에게는 그럴 필요가 없어요. --compact-output 옵션은 서식용 공백을 제거해서 결과 JSON의 크기를 최소화해요.

  • -f filename 또는 --from-file filename

    명령줄에서 직접 제공하는 대신 filename에서 jq 프로그램을 읽어요. sed와 awk도 같은 목적으로 -f 옵션을 사용해요. 연습 문제의 테스트 스크립트에서 이 옵션이 사용되는 것을 보게 될 거예요.

모든 옵션에 대한 자세한 내용은 매뉴얼을 참고해요.

필터와 파이프

필터는 표현식이라고도 해요.

_필터_는 입력을 받아 출력을 만들어요. 유닉스 셸에서 작업하는 방식처럼, 파이프 |로 _필터_를 이어서 한 _필터_의 출력을 다른 _필터_의 입력으로 연결할 수 있어요.

항등 필터: .

가장 단순한 _필터_예요. 입력을 그대로 출력으로 전달하기만 해요. 예를 들어, jq는 기본적으로 보기 좋게 출력하므로, JSON을 . 필터에 전달하면 보기 좋게 포맷된 출력을 공짜로 얻을 수 있어요!

$ echo '[1, 2, 3]' | jq '.'
[
  1,
  2,
  3
]

배열

배열을 다루는 방법을 간단히 소개할게요. 이 주제는 나중에 더 자세히 다뤄요.

_배열_의 원소는 대괄호로 접근하며, 인덱스는 0부터 시작해요.

$ echo '[10, 20, 30]' | jq '.[1]'
20

_필터_는 표현식을 [와 ]로 감싸서 _배열_을 만들 수 있어요.

  • 알려진 원소 목록으로 만드는 경우:

    jq -n '[1, 2, 3]'
    
  • 원소의 스트림을 모으는 경우: 예를 들어, range는 숫자의 스트림을 출력하는 함수예요

    $ jq -n 'range(10; 70; 15)'
    10
    25
    40
    55
    

    []를 사용하면 표현식의 결과를 배열로 모아요

    $ jq -c -n '[range(10; 70; 15)]'
    [10,25,40,55]
    

쉼표는 연산자예요

_쉼표_는 _배열_의 원소를 구분하는 문법만이 아니에요. 쉼표는 스트림을 이어 붙이는 연산자예요.

예를 들어 [1, 2, 3]은 배열 생성자 []를 사용해서 세 표현식 1, 2, 3을 이어 붙인 결과를 모으는 _필터_예요.

위의 range(10; 70; 15)에서 세미콜론을 눈치챘나요? jq 언어에서 _쉼표_는 특별한 용도가 있기 때문에, 여러 인자를 받는 함수는 인자를 구분할 때 세미콜론을 사용해요.

객체

객체를 간단히 소개할게요.

많은 프로그래밍 언어와 비슷하게, 점을 사용해서 _객체_의 속성에 접근해요.

$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"

대괄호를 _객체_에도 사용할 수 있지만, 그럴 때는 문자열 리터럴에 따옴표가 필요해요. 공백이 들어간 키를 다루는 한 가지 방법이에요.

$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"

{}와 key: value 쌍을 사용해서 _객체_를 만들 수 있어요. "단순한" 문자열인 _키_에는 따옴표가 필요하지 않아요.

jq -n '{question: (6 * 9), answer: 42}'

다음과 같이 출력해요

{
  "question": 54,
  "answer": 42
}

_키_를 _표현식_으로 취급하려면 괄호로 감싸야 해요 (아래도 위와 같은 결과를 출력해요).

echo '[["question", "answer"], [54, 42]]' \
| jq '{(.[0][0]): .[1][0], (.[0][1]): .[1][1]}'
Note

큰 객체에서 키의 일부만 추출하고 싶은 경우가 꽤 흔해요. 예를 들어, 다음에서 id와 name을 추출하려면

{
    "id": 101,
    "name": "alpha widget",
    "specifications": {...}
}

이렇게 작성할 수 있어요

{id: .id, name: .name}

하지만 이건 너무 흔한 패턴이라 축약 문법이 있어요:

{id, name}

파이프라인

예를 들어, 다음 내용이 담긴 file.json이 있다고 해요

{
  "key1": "value1",
  "key2": [5, 15, 25]
}

key2 _배열_의 길이를 계산해볼까요:

$ jq '.key2 | length' file.json
3

.key2 표현식의 출력을 length의 입력으로 _파이프_하고 있어요. 예상대로 이 함수는 배열의 원소 개수를 출력해요.

Caution

이 부분은 jq에 익숙해지는 데 시간이 좀 걸리는 측면이에요. 대부분(전부는 아니지만)의 함수는 데이터를 인자가 아니라 필터의 입력으로 전달하는 _필터_처럼 동작해요.

필터는 입력을 무시할 수 있어요

이 예제에서는 입력 JSON 데이터가 무시되고 출력에 아무 영향을 주지 않아요:

$ echo '{"answer": 42}' | jq '6 * 9'
54

필터는 데이터의 스트림을 출력할 수 있어요

_필터_는 값을 하나 이상 출력할 수 있어요. 예를 들어, .[] _필터_는 _배열_의 각 원소를 별개의 값으로 출력해요:

$ jq -n -c '[1, 2, 3]'
[1,2,3]

$ jq -n -c '[1, 2, 3] | .[]'
1
2
3

그런 _필터_를 다른 _필터_로 파이프하면 두 번째 _필터_가 각 값마다 실행돼요:

$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6

이것은 암시적 반복과 비슷해요. 이 기법을 이해하고 나면, 아주 강력한 jq _필터_도 매우 간결하게 작성할 수 있다는 걸 알게 될 거예요.

괄호

괄호는 다른 언어에서처럼 연산 순서를 강제하기 위해 하위 표현식들을 묶는 데 사용해요. jq에서는 괄호가 필요하다는 사실이 다소 의외로 느껴질 수 있어요.

예를 들어, 원소 두 개짜리 _배열_을 만들고 싶다고 해요. 9의 제곱근, 그리고 _e_의 1제곱이에요. 두 개별 표현식은 9 | sqrt와 1 | exp예요. 출력이 배열 [3, 2.7...]이기를 기대해요

$ jq -n '[ 9|sqrt, 1|exp ]'
[
  20.085536923187668,
  2.718281828459045
]

왜 기대한 결과가 나오지 않았을까요? jq는 그것을 이렇게 해석해요:

[ ((9|sqrt), 1) | exp ]

jq는 두 원소(3과 1)의 스트림을 만들고, 각각을 exp에 전달해요.

exp가 숫자 하나만 입력으로 받도록 해야 해요. 다시 말해, 파이프가 쉼표보다 먼저 평가되도록 강제해야 해요.

$ jq -n '[ 9|sqrt, (1|exp) ]'
[
  3,
  2.718281828459045
]

타입

매뉴얼에서

jq는 JSON과 동일한 데이터 타입 집합을 지원해요. 숫자, 문자열, 불리언, 배열, 객체(JSON 용어로는 문자열 키만 가지는 해시), 그리고 "null"이에요.

이것들에 대해서는 이후 연습 문제에서 더 배우게 될 거예요.

공백

jq에서는 공백이 중요하지 않아요. 코드를 포맷할 때 원하는 대로 공백, 탭, 줄바꿈을 사용해요. 아직 알려진 jq 스타일 가이드는 없어요.

불변 값

jq의 값은 _불변_이에요. 값을 수정하는 필터는 새로운 값을 출력해요. 이는 jq에 전역 변수가 없다는 뜻이에요. 상태를 한 필터에서 다른 필터로 전달하는 방식에 익숙해져야 해요.

참으로 취급되는 값

false와 null 값은 거짓으로 간주해요. 그 밖의 모든 값 (숫자 0과 빈 문자열/배열/객체 포함)은 참이에요.

함수와 연산자

깊이 들어가지는 않고(함수는 다른 연습 문제에서 다룰 주제예요), 여기 몇 가지 유용한 내장 함수를 소개할게요:

  • length

    배열을 입력으로 받으면, 배열의 원소 개수를 출력해요.

    $ jq -n '[10, 20, 30, 40] | length'
    4
    
  • +

    이 연산자는 피연산자의 타입에 따라 하는 일이 달라요. 숫자는 더하고, 문자열은 이어 붙이고, 배열은 뒤에 추가하고, 객체는 병합해요.

    $ jq -c -n '
        3 + 4,
        "foo" + "bar",
        ["a", "b"] + ["c"],
        {"m": 10} + {"n": 20}
    '
    7
    "foobar"
    ["a","b","c"]
    {"m":10,"n":20}
    

    add는 배열을 받아서, 모든 원소를 +의 규칙으로 합친 값을 반환하는 함수예요. [1, 2, 3] | add는 6을 출력해요.

  • map

    배열을 입력으로, 필터를 인자로 받으면, 각 원소에 필터를 적용한 배열을 출력해요

    $ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
    [2,4,6,8]
    
  • select

    어떤 입력과 필터를 인자로 받으면:

    • 인자에 적용한 필터가 참 값을 내면, 입력을 그대로 출력해요
    • 그렇지 않으면 아무것도 출력하지 않아요 (null 값이 아니라 정말로 아무 출력도 없어요)

    예를 들어, 어떤 숫자들이 주어졌을 때 3으로 나누어 떨어지는 것들을 선택해요

    $ jq -n 'range(10) | select(. % 3 == 0)'
    0
    3
    6
    9
    

    range는 숫자의 _스트림_을 출력한다는 걸 기억해요. select는 각 숫자마다 한 번씩 호출돼요. 표현식을 "통과"하는 숫자만 출력돼요.

    배열의 원소를 선택해야 하는 경우가 자주 있어요. 이를 위한 방법이 몇 가지 있어요.

    입력 ["Anne", "Bob", "Cathy", "Dave"]가 주어졌을 때, 길이가 4인 이름을 선택해요.

    • map과 select를 함께 사용하기

      map(select(length == 4))
      
    • 배열을 원소들로 펼친 뒤, 그 스트림에 select를 적용하고, 결과를 모으기

      [ .[] | select(length == 4) ]
      

주석

주석은 # 문자로 시작하고 줄 끝까지 이어져요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요

기초 배우기