פענוח מתגים באמצעות getopts

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

1. מתגים ביוניקס

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

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


$ ls -rt -l -S

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

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

 

2. מינוחים

ניקח את הפעלת התוכנית הבאה:


mybackup -x -f /etc/mybackup.conf -r ./foo.txt bar.txt

התוכנית מקבלת מספר מתגים ואובייקטים:

המתג -x הינו מתג בוליאני. התוכנית בודקת את נוכחותו או היעדרו.

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

הטקסט bar.txt הינו אובייקט עליו התוכנית תעבוד.

כעת שאנו מבינים את המינוחים, נמשיך לפקודת getopts ונראה כיצד לפענח מתגים אלו.

3. פענוח מתגים

הפקודה getopts מקבלת רשימה של מתגים אפשריים ומחזירה את המתג המפוענח הבא, או כשלון אם נגמרו המתגים. באופן זה היא משתלבת טוב עם לולאת while. הפקודה getopts פועלת על משתני הפרמטרים $1, $2, $3 וכן הלאה, אך אינה משנה אותם— לכן כשמפעילים אותה בלולאה עלינו להזיז את המשתנים בעצמנו ובכל איטרציה למחוק את המתגים ש getopts כבר פענחה.

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

עבור הפקודה שראינו בסעיף הקודם, נוכל להשתמש בהפעלה הבאה:


getopts xf:r:

המציינת מתג בוליאני -x ושני מתגים המצפים לפרמטרים -f ו -r.

שילוב של getopts בתוך סקריפט נראה כך:


#!/usr/bin/env bash

while getopts "xf:r:" opt; do
  case $opt in
    x)
      echo "found -x !";;
    f)
      echo "found -f. argument was: $OPTARG";;
    r)
      echo "found -r. argument was: $OPTARG";;
  esac
done

 

4. דוגמא מסכמת

ברצוננו לכתוב סקריפט המציג הודעה למסך מספר פעמים. המתגים שהסקריפט צריך לתמוך בהם:
-c מתג המקבל פרמטר ומציין כמה פעמים להדפיס את ההודעה (אם לא הופיע נציג 5 פעמים).
-o מצג המקבל פרמטר ומציין לאיזה קובץ לכתוב את ההודעה (אם לא הופיע נכתוב למסך).
-l מתג בוליאני, אם הופיע נדפיס הודעה עם סימוני כוכביות לפני ואחרי.

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

פתרון:


#!/usr/bin/env bash

COUNT=5
MESSAGE="Hello World"

while getopts "c:o:l" opt; do
  case $opt in
    c)
      COUNT=$OPTARG;;
    o)
      OUTFILE=$OPTARG;;
    l)
      PADDING=1;;
  esac
done

shift $((OPTIND-1))

# One-line if:
# if $1 is not empty -> change message
[[ ! -z $1 ]]   && MESSAGE="$*"

# if PADDING -> pad message
[[ $PADDING = 1 ]] && MESSAGE="****** $MESSAGE ******"

# truncate OUTFILE if it's not empty
[[ ! -z $OUTFILE ]] && :> $OUTFILE

for (( i=0; i < COUNT; i++ )) do
  if [[ -z $OUTFILE ]]
  then
    echo "$MESSAGE"
  else
    echo "$MESSAGE" >> $OUTFILE
  fi
done

כמה נקודות לשים לב אליהן, לגבי getopts וגם בנוסף:

  1. לאחר פענוח כל הפרמטרים, אנו משתמשים בפקודה shift כדי להסיר את המתגים מרשימת הפרמטרים. כך ניתן בהמשך להשתמש ב-$1, $2 ו $* בלי שיופיעו ברשימה המתגים.
  2. getopts מתקשרת עם שאר התוכנית באמצעות משתנים: אנו מגדירים משתנים וערכי ברירות מחדל בתחילת התוכנית, ובקוד הטיפול במתגים משנים את הערכים. במהלך התוכנית ניתן להשתמש במשתנים אלו.
  3. מחיקת תוכן הקובץ בלבד יעילה יותר מאשר מחיקה ויצירה מחדש (וגם לפעמים אין לנו הרשאות למחוק קובץ אך יש הרשאה לכתוב אליו). ב Bash הפקודה : היא הפקודה הריקה, ולכן ניתן לרוקן קובץ על ידי ניתוב פלט הפקודה הריקה אליו.

 

5. תרגול

  1. Write a shell script that asks the user for a file extension (select from all existing extensions in the directory) and prints how many files in the directory have that extension
  2. Modify the previous script to take the extension using a command line switch. Running example: count_files -e mp3 should print how many mp3 files are in the directory. Running without the switch should perform the same behaviour as the original script.
  3. Write a shell script that takes a file name and a new name, and changes all files with the given name (recursively in all folders) to the new name. The script should accept a boolean switch -n. When running with -n it should just print all the mv operations without running them.