مب

مبانی در 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

    برنامه‌ی jq را از filename بخوانید، به‌جای اینکه آن را روی خط فرمان بنویسید. هم sed و هم awk برای همین منظور از گزینه‌ی -f استفاده می‌کنند. این را در اسکریپت‌های تست تمرین‌های عملی خواهید دید.

برای جزئیات همه‌ی گزینه‌ها، راهنما را ببینید.

فیلتر و لوله

فیلترها با نام عبارت‌ها هم شناخته می‌شوند.

یک فیلتر ورودی می‌گیرد و خروجی تولید می‌کند. مثل همان روشی که در یک پوسته‌ی یونیکس کار می‌کنید، می‌توانید فیلترها را با لوله‌ی | به هم بپیوندید تا خروجی یک فیلتر به ورودی فیلتر دیگر وصل شود.

فیلتر همانی: .

این ساده‌ترین فیلتر است. ورودی‌اش را همان‌طور که هست به خروجی می‌فرستد. برای نمونه، jq به‌طور پیش‌فرض خروجی را مرتب چاپ می‌کند، پس اگر JSON را به فیلتر . بدهید، خروجی مرتب و قالب‌بندی‌شده را مجانی به‌دست می‌آورید!

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

آرایه

این آشنایی سریعی است با کار کردن با آرایه‌ها. بعداً این موضوع را با جزئیات بیشتری بررسی خواهیم کرد.

به عناصر آرایه با کروشه دسترسی پیدا می‌کنید و اندیس‌گذاری از صفر شروع می‌شود.

$ 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، ممکن است نیاز به آن‌ها تا حدودی غیرمنتظره به نظر برسد.

برای نمونه، فرض کنید می‌خواهیم آرایه‌ای با ۲ عنصر بسازیم: جذر ۹؛ و e به توان ۱. دو عبارت جداگانه این‌ها هستند: 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 به‌عنوان «غلط» در نظر گرفته می‌شوند. هر مقدار دیگری (از جمله عدد صفر و رشته/آرایه/شیء خالی) «درست» است.

تابع و عملگر

بدون اینکه خیلی عمیق شویم (توابع موضوع تمرینی دیگر خواهند بود)، در ادامه چند تابع درونی مفید را می‌بینید:

  • 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، بلکه واقعاً هیچ خروجی‌ای)

    برای نمونه، با گرفتن چند عدد، آن‌هایی که بر ۳ بخش‌پذیرند را انتخاب کنید

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

    به یاد بیاورید که range یک جریان از اعداد خروجی می‌دهد. select برای هر عدد یک بار فراخوانی می‌شود. فقط اعدادی که از عبارت «عبور می‌کنند» خروجی داده می‌شوند.

    اغلب لازم است عناصری از یک آرایه را انتخاب کنید. برای این کار چند روش وجود دارد.

    با ورودی ["Anne", "Bob", "Cathy", "Dave"]، نام‌هایی با طول ۴ را انتخاب کنید.

    • از map و select با هم استفاده کنید

      map(select(length == 4))
      
    • آرایه را به عناصر جدا از هم بشکنید، روی آن جریان select بزنید و نتایج را گرد آورید

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

توضیح

توضیح‌ها با کاراکتر # شروع می‌شوند و تا پایان خط ادامه دارند.

ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود

مبانی را یاد بگیرید