שלוש דוגמאות חמודות ליצירת JSON-ים עם jo

08/02/2022

הפקודה jo יודעת לייצר פלט בפורמט JSON לפי הוראות שאנחנו מעבירים לה בשורת הפקודה. זה נשמע פשוט אבל האמת שבזכות אינסוף האפשרויות שלה היא כלי שמאוד חשוב להכיר. בואו נראה שלוש דוגמאות חמודות לשימוש בפקודה כדי לקבל דברים שבלעדיה אולי היה קשה.

1. אבל קודם jo בשלוש מילים

אם קרה לכם שרציתם לכתוב קובץ בפורמט JSON והסתבכתם איפה לשים את המרכאות, אז אני בטוח שתדעו להעריך כלי אוטומטי שמטפל בכתיב התקין במקומכם. הפקודה jo בשימוש הבסיסי היא כזה כלי.

אני מפעיל אותה משורת הפקודה ונותן לה רשימה של מפתחות וערכים כדי לקבל אובייקט json:

$ jo one=10 two=20 three=30
{"one":10,"two":20,"three":30}

מפתחות שמסתיימים בסימן סוגריים מרובעים יהפכו למערך:

$ jo "a[]=10" "a[]=20" "a[]=30" b=hello
{"a":[10,20,30],"b":"hello"}

אבל הרבה פעמים נשתמש במתג -a כדי ליצור מערך:

$ jo -a blue green white
["blue","green","white"]

ובקצת קסמי bash כדי לשלב מערכים עם אוביקטים:

$ jo name=ynon numbers=$(jo -a 10 20 30)
{"name":"ynon","numbers":[10,20,30]}

אז זה היה פשוט ובאמת רק עד לפה jo נשמע כמו אחלה כלי שעוזר לבנות JSON-ים, אבל כמו שנראה בשלושת הדוגמאות הבאות - הוא יכול להיות הרבה יותר מזה.

2. חיבור כל השרתים בקובץ hosts לפי כתובת IP

נניח שיש לנו קובץ /etc/hosts עם השורות הבאות:

127.0.0.1 localhost
127.0.0.1 mysite
192.168.0.4 hd
192.168.1.10 router
192.168.1.10 player

ואנחנו רוצים להפוך את זה ל JSON בו כל מפתח הוא כתובת IP והערך הוא מערך של כל השמות של אותה כתובת IP. הכלי הראשון שקופץ לראש הוא כמובן jo.

בגלל שהערך צריך להיות מערך אני רוצה להעביר ל jo קלט בו המפתח יהיה כתובת ה IP, אחריה סימן סוגריים מרובעים כדי לציין שזה מערך ואז סימן שווה ואז הערך. אני יכול להשתמש ב awk כדי להמיר את הפורמטים:

$ acat hosts | awk '{print $1"[]="$2}'
127.0.0.1[]=localhost
127.0.0.1[]=mysite
192.168.0.4[]=hd
192.168.1.10[]=router
192.168.1.10[]=player

ובשליחה ל jo נקבל:

$ cat hosts | awk '{print $1"[]="$2}'|jo |jq

{
  "127.0.0.1": [
    "localhost",
    "mysite"
  ],
  "192.168.0.4": [
    "hd"
  ],
  "192.168.1.10": [
    "router",
    "player"
  ]
}

3. חיבור כל התהליכים על המכונה לקובץ JSON היררכי

עכשיו ננסה משחק דומה עם תהליכים על המכונה. אני יודע ש ps מסוגל להדפיס שורה לכל תהליך, ויודע להדפיס לכל תהליך מהו מזהה התהליך שיצר אותו. זה נראה ככה:

$ ps -e -o ppid,pid,comm|head

 PPID   PID COMM
    0     1 /sbin/launchd
    1    89 /usr/libexec/logd
    1    90 /usr/libexec/UserEventAgent
    1    92 /System/Library/PrivateFrameworks/Uninstall.framework/Resources/uninstalld
    1    93 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/Support/fseventsd
    1    94 /System/Library/PrivateFrameworks/MediaRemote.framework/Support/mediaremoted
    1    97 /usr/sbin/systemstats
    1    98 /usr/libexec/configd
    1   100 /System/Library/CoreServices/powerd.bundle/powerd

יכול להיות מעניין לראות את הרשימה הזאת בתור אוביקט JSON, כאשר המפתח הוא מזהה תהליך והערך הוא אוביקט שבו כל מפתח הוא מזהה תהליך ילד - שנוצר מתוך התהליך שמעליו בהיררכיה.

בדוגמה שהדפסתי זה יהיה האוביקט:

{
  "0": {
    "1": "/sbin/launchd"
  },
  "1": {
    "89": "/usr/libexec/logd",
    "90": "/usr/libexec/UserEventAgent",
    "92": "/System/Library/PrivateFrameworks/Uninstall.framework/Resources/uninstalld",
    "93": "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/Support/fseventsd",
    "94": "/System/Library/PrivateFrameworks/MediaRemote.framework/Support/mediaremoted",
    "97": "/usr/sbin/systemstats",
    "98": "/usr/libexec/configd",
    "100": "/System/Library/CoreServices/powerd.bundle/powerd",
    "101": "/usr/libexec/IOMFB_bics_daemon"
  }
}

איך מייצרים כזה אוביקט אתם שואלים? הפעם אני משתמש ביכולת נוספת של jo שהיא קבלת מפתחות מקוננים. שימו לב לפקודה:

$ jo -d . foo.bar.buz=10

{"foo":{"bar":{"buz":10}}}

המתג -d מאפשר לי לציין תו הפרדה בין רמות היררכיה במחרוזת המפתח. אני בחרתי נקודה ולכן jo מפצל את המפתח foo.bar.buz להיררכיה של שלושה אוביקטים, אחד בתוך השני.

את אותו הגיון אני יכול להפעיל על רשימת התהליכים כדי לקבל בדיוק את האוביקט שרציתי. אני רק צריך להשתמש ב awk כדי להפוך את הרווח לנקודה:

ps -e -o ppid,pid,comm|tail +2 | awk '{print $1"."$2"="$3}'|jo -d.

4. חיבור הגדרות מכל ה JSON-ים בתיקייה לקובץ אחד

דוגמה אחרונה לפוסט משתמשת בעוד יכולת של jo שהיא קריאת חלק מאוביקט מקובץ. אם יש לי בתיקיה קובץ בשם a.json עם התוכן הבא:

{"window":"one","top":10,"left":20}

אז אני יכול להשתמש ב jo כדי לשתול את כל תוכן הקובץ בתור תת-עץ באוביקט שאני יוצר:

$ jo -p name=ynon data=:a.json

{
  "name": "ynon",
  "data": {
    "window": "one",
    "top": 10,
    "left": 20
  }
}

(נ.ב. שימו לב ל -p שגורם ל jo להדפיס את ה json מחולק לשורות ועם רווחים).

אז עכשיו נדמיין שיש לנו מספר קבצי json בתיקיה ואנחנו רוצים לאחד את כל התוכן שלהם לקובץ json יחיד. בגלל שחלק מהמפתחות עלולים להיות כפולים, אני רוצה לשמר את ההיררכיה של התיקיה בתוך קובץ ה JSON. כלומר אם יש לי בתיקיה את a.json עם התוכן שדיברנו עליו, ובתת תיקיה בשם bar יש לי קובץ בשם b.json עם תוכן אחר, אז אני רוצה לקבל אוביקט json שנראה כך:

{
    a.json: { ... contents of file a.json },
    bar: {
        b.json: {
            ... contents of file b.json
        }
    }
}

איך בונים את הקובץ? עם jo כמובן. אני מחפש את כל קבצי ה json בתיקיה ובכל תתי התיקיות שלה עם find, ומדפיס את התוצאה עם סימן הנקודותיים:

$ find . -type f -name '*.json' -exec echo {}=:{} \;

./a.json=:./a.json
./bar/b.json=:./bar/b.json

עכשיו אני משלב את -d כדי ליצור אוביקטים מקוננים ואת הנקודותיים כדי לקרוא תוכן של קובץ ומקבל:

$ find . -type f -name '*.json' -exec echo {}=:{} \; | jo  -p -d '/'

{
   ".": {
      "a.json": {
         "window": "one",
         "top": 10,
         "left": 20
      },
      "bar": {
         "b.json": {
            "window": "two",
            "top": 30,
            "left": 50
         }
      }
   }
}

יש עוד המון מה להגיד על jo אבל אני לא פה בשביל להחליף את התיעוד. אם אהבתם את הכלי ורוצים לדעת עליו יותר שווה להעיף מבט גם בדף התיעוד שלהם בקישור https://github.com/jpmens/jo/blob/master/jo.md.