• בלוג
  • שמונה דוגמאות ל Ruby One Liners משורת הפקודה ביוניקס

שמונה דוגמאות ל Ruby One Liners משורת הפקודה ביוניקס

20/04/2022

רובי אומנם נחשבת לשפה High Level שעוזרת למתכנתים לכתוב קוד קריא, אבל לרובי יש גם צד אפל במשפחה וזו המורשת שהיא קיבלה מ perl. בדומה ל perl, גם לרובי יש מצב שורת פקודה שבעזרת כמה מתגים בהרצה יאפשר לכם כוחות על יוניקסאיים. הנה 8 דוגמאות להפעלת רובי משורת הפקודה-

1. הדפסת 10 שורות ראשונות מהקלט - בסגנון head

כשמפעילים את רובי משורת הפקודה אנחנו בדרך כלל מעבירים בתור פרמטר שם של קובץ שמכיל תוכנית רובי. אבל אם במקום שם קובץ נעביר את המתג -e אז רובי ייקח את הדברים הבאים שנכתוב בשורת הפקודה בתור התוכנית שצריך להריץ.

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

ואם נוסיף לזה -p אז רובי ידמיין שיש שם גם פקודה הדפסה.

לרובי, כמו לפרל, יש משתנים גלובאליים שאומרים לנו כל מיני דברים לגבי הקלט והמקום שלנו בו. לדוגמה המשתנה הגלובאלי $. מכיל את מספר שורת הקלט הנוכחית.

אם אני משלב את כל אלה לפקודה אחת משורת הפקודה, נוכל לראות שהשורה הבאה מדפיסה את 10 השורות הראשונות מהקלט שהיא מקבלת ואז עוצרת:

$ ruby -n -e 'print if $. <= 10'

הפקודה יכולה לקבל קלט מקובץ או מ Pipe, כלומר אפשר לכתוב:

$ ls -l | ruby -n -e 'print if $. <= 10'

או:

$ ruby -n -e 'print if $. <= 10' < /etc/passwd

אגב - הפקודה הבאה שקולה פונקציונאלית אבל יעילה יותר על קבצים גדולים:

$ ruby -p -e 'exit if $. > 10'

שימו לב איך -p גורם לרובי להדפיס באופן אוטומטי כל שורת קלט שהוא נתקל בה, ולכן מספיק לקוד שלי לזהות מתי צריך להפסיק ולצאת מהתוכנית.

2. הדפסת השורה השניה והלאה מהקלט

אומנם head לא יודע "לדלג" על n שורות ראשונות, אבל עם רובי אין לנו שום בעיה לשחק עם הסימנים:

$ ls -l | ruby -p -e 'next if $. < 2'

3. הדפסת 10 שורות אחרונות מהקלט - בסגנון tail

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

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

$ ls -l | ruby -e 'puts readlines.last(10)'

4. הדפסת כל השורות מהסוף להתחלה

ועם אותו טריק אני יכול להפוך את השורות בקלט לגמרי - כאילו שמנו מראה אחרי השורה האחרונה:

$ cowsay Hello World | ruby -e 'puts readlines.reverse'
                ||     ||
                ||----w |
            (__)\       )\/\
         \  (oo)\_______
        \   ^__^
 -------------
< Hello World >
 _____________

5. סיכום המספרים בעמודה החמישית של הקלט כדי לגלות את הגודל הכולל של הקבצים בתיקיה

הפקודה ls -l מדפיסה רשימת קבצים מחולקת לעמודות ובעמודה החמישית היא כותבת את גודל הקובץ. לדוגמה בפרויקט רובי יכול להיות שנקבל את הפלט הבא:

$ ls -l
total 72
-rw-r--r--   1 ynonp  staff  2618 Mar 26 09:43 Gemfile
-rw-r--r--   1 ynonp  staff  7368 Mar 22 22:11 Gemfile.lock
-rw-r--r--   1 ynonp  staff    74 Mar 21 17:13 README.md
-rw-r--r--   1 ynonp  staff   227 Mar 21 17:12 Rakefile
drwxr-xr-x  12 ynonp  staff   384 Mar 21 22:36 app
drwxr-xr-x   7 ynonp  staff   224 Mar 21 17:12 bin
drwxr-xr-x  17 ynonp  staff   544 Apr  8 19:09 config
-rw-r--r--   1 ynonp  staff   160 Mar 21 17:12 config.ru
drwxr-xr-x   6 ynonp  staff   192 Apr 11 10:03 db
drwxr-xr-x  14 ynonp  staff   448 Apr 12 10:55 design
drwxr-xr-x   3 ynonp  staff    96 Mar 21 17:15 doc
drwxr-xr-x   3 ynonp  staff    96 Mar 21 22:48 engines
drwxr-xr-x   4 ynonp  staff   128 Mar 21 17:12 lib
drwxr-xr-x   5 ynonp  staff   160 Mar 22 21:54 log
drwxr-xr-x   9 ynonp  staff   288 Apr  3 15:49 public
drwxr-xr-x   3 ynonp  staff    96 Mar 21 17:12 storage
drwxr-xr-x  14 ynonp  staff   448 Mar 26 09:53 test
drwxr-xr-x   9 ynonp  staff   288 Mar 26 08:47 tmp
-rw-r--r--   1 ynonp  staff   804 Apr  1 08:46 users.yml
drwxr-xr-x   4 ynonp  staff   128 Mar 21 17:12 vendor
-rw-r--r--   1 ynonp  staff    64 Apr  1 08:46 workshop_instances.yml
-rw-r--r--   1 ynonp  staff   171 Apr  1 08:46 workshops.yml

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

$ ls -l | ruby -na -e 'BEGIN { $s = 0 }; END { print $s }; $s += $F[4].to_i'

הפעם עלינו מדרגה ברמת המורכבות בגלל שני הבלוקים BEGIN ו END שאולי מוכרים לכם מ awk. בלוק BEGIN רץ לפני שרובי מתחיל לעבוד ו END אחרי שהקלט נגמר. בגלל שמשתנים מאותחלים ברובי ל nil, אני צריך להגדיר ולאתחל משתנה גלובאלי שיחזיק את הסכום וכל פעם להגדיל אותו עד ההדפסה בסיום.

דרך קלה לפשט ביטויים כאלה היא לפצל אותם לשניים - החלק הראשון ייקח את העמודה החמישית, והחלק השני יסכום את השורות:

$ ls -l | ruby -na -e 'puts $F[4]' | ruby -e 'puts readlines.map(&:to_i).sum'

6. תדירות הופעת מילים בקלט

הפקודה tally של רובי יודעת לספור דברים. היא מקבלת מערך ואומרת כמה פעמים כל איבר מופיע במערך זה. בשביל לספור כמה פעמים כל מילה מופיעה בקלט אני יכול לשמור במערך את כל המילים שאני מוצא ואז בבלוק END להדפיס את ה tally של הרשימה שבניתי. לדוגמה אם יש לי קובץ בשם text עם התוכן:

some gemstones that are popularly or historically called rubies
such as the black prince ruby in the british imperial state crown
are actually spinels
these were once known as balas rubies

אוכל לדעת כמה פעמים כל מילה הופיעה בקלט עם הפקודה:

$ cat text| ruby -a -n -e 'BEGIN { $words=[] }; $words += $F; END { puts $words.tally }'

{"some"=>1, "gemstones"=>1, "that"=>1, "are"=>2, "popularly"=>1, "or"=>1, "historically"=>1, "called"=>1, "rubies"=>2, "such"=>1, "as"=>2, "the"=>2, "black"=>1, "prince"=>1, "ruby"=>1, "in"=>1, "british"=>1, "imperial"=>1, "state"=>1, "crown"=>1, "actually"=>1, "spinels"=>1, "these"=>1, "were"=>1, "once"=>1, "known"=>1, "balas"=>1}

7. איתור ה Shell הפופולרי ביותר בקובץ `/etc/shells`

הקובץ /etc/shells על מכונת יוניקס מפרט את כל תוכניות ה shell שמותקנות על המכונה. תוכן לדוגמה של הקובץ יכול להיות:

# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/dash
/usr/local/bin/bash
/bin/sh
/bin/tcsh
/bin/zsh
/usr/local/bin/pwsh

שימו לב שבמקרה שלנו bash מופיע פעמיים כי הוא מותקן משתי תיקיות שונות. אם נדמיין קובץ יותר גדול, אולי נרצה לדעת איזה Shell מותקן הכי הרבה פעמים על המערכת, או לפחות כמה פעמים מותקן כל Shell על המערכת. ורובי עדיין כאן כדי לעזור לנו.

המתג -F מאפשר לי לקבוע מה יהיה תו ההפרדה בין המילים בקלט. אכוון אותו ל / ואני מקבל:

$ cat shells | ruby -F/ -na -e 'puts $F[2] if $F.size > 1' | ruby -e 'puts readlines.map(&:chomp).tally'

{"bash"=>1, "csh"=>1, "dash"=>1, "ksh"=>1, "sh"=>1, "tcsh"=>1, "zsh"=>1, "local"=>2}

8. הוספת `-e` לשורת ה shbang של הקובץ

המתג -e ב sh גורם ל Shell Script לצאת מיד אם פקודה שהוא הריץ נכשלה. לדוגמה הסקריפט הבא יכתוב the end אפילו שהוא לא הצליח ליצור ספריה חדשה בשורש העץ כי לא היו לו הרשאות:

#!/bin/bash

mkdir /foo

echo the end

אבל אם נוסיף לשורה הראשונה את המתג -e אז הפלט יסתיים בשורת השגיאה של ה mkdir.

אבל מה אם כבר יש לכם ערימה של סקריפטים שאין להם -e ואתם רוצים להוסיף את המתג לכולם? ניעזר ברובי כמובן. המתג -i גורם לרובי לשנות את קובץ הקלט עצמו שהוא קיבל, ולכן אם יש לי סקריפט בשם a.sh אני יכול להפעיל את הפקודה הבאה כדי להוסיף לו אוטומטית את ה -e בסוף השורה הראשונה:

$ ruby -p -i.bak -e 'sub(/$/, " -e") if $. == 1' a.sh

ואם יש לי ערימה של קבצים כאלה אני יכול לשלב את הפקודה עם find ולהוסיף -e לכל קבצי ה .sh בתיקיה:

find . -type f -name '*.sh' -exec ruby -p -i.bak -e 'sub(/$/, " -e") if $. == 1' {} \;