תוכן עניינים הקדמה
הכרות עם schemes
תבניות נתונים
תבניות
משפטי בקרה
משתנים לקסיקלים
רקורסיה
קלט/פלט
macros
מבנים
alises and tables ממשק המערכת
מחלקות ואובייקטים
jumps
אי-דטרמיניסטיות
מנועים
shell scripts
אתר ללימוד מקיף> פרק 16 - Shell scripts

פרק 16 - Shell scripts


הנושאים בפרק זה:
16.1.     חוזרים ל-!hello, world
16.2.     script עם ארגומנטים
16.3.     דוגמא


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

מערכות הפעלה כמו Unix ו-Dos (ממשק ה-command-line מסופק ב-Windows) מספקות מנגנון כזה של scripts, אך שפת ה-scripting בכל אחד מהם אינה מפותחת כ"כ. לעיתים script הוא רק רצף או איחוד של כמה פקודות שהיינו יכולים להקיש בהינתן ה-prompt של ה-Shell. הדבר חוסך מהמשתמש להקיש כל אחת מפקודות ה-Shell בנפרד כל פעם שנזקקים לאותה סדרת פקודות.
כמה משפות ה-script מוסיפות מעט אפשרויות תכנות נוספות בתבניות של התניות או לולאות, אך בכך מסתכם העניין. זה מספיק עבור משימות קטנות יחסית, אך כשה-script גדל ונעשה תובעני יותר, כפי ש-scripts צריכים להיות, מורגש הצורך בשפת תכנות בשלה יותר. Scheme עם ממשקים הולמים למערכת ההפעלה מאפשרת יצירת scripts בקלות ובאופן שניתן יהיה לתחזק.

בפרק זה יתואר איך לכתוב scripts ב-Scheme. מאחר ויש וריאציות רבות בגרסאות Scheme לגבי ביצוע העניין, אנו נתרכז בגרסת MzScheme, ונתעד בנספח A את השינויים שיידרשו כדי להתאים לגרסאות האחרות. כמו כן נתרכז כעת במערכת ההפעלה Unix, נספח B יעסוק במקבילה DOS.



לתחילת העמוד

16.1 חוזרים ל-!Hello, World

כעת ניצור script ב-Scheme האומר שלום לעולם. אמירת שלום, כמובן, אינה בעיית scripting תובענית עבור שפות ה-scripting המסורתיות. בכל אופן, ההבנה איך לתעתק זאת ל-Scheme תפתח עבורנו את המסלול ל-scripts דורשי מאמץ נוסף. ראשית, hello-script שגרתי ב-Unix הוא קובץ בעל התוכן הבא:

echo Hello, World!

נעשה כאן שימוש בפקודת ה-Shell echo. ה-script יכול להיקרא hello ותתכן בו פעולת ההרצה ע"י שנרשום:

chmod +x hello

ונמקם אותו באחת הספריות המוזכרות במשתנה הסביבתי PATH. לאחר מכן, כל פעם שנקיש:
hello ב-prompt של ה-Shell, נקבל מיד את ברכת השלום.
ה-hello-script ייתן בדיוק את אותו הפלט כמו תכנית scheme רגילה (התכנית בפרק 1), אך אנו צריכים משהו בקובץ שיידע את מערכת ההפעלה שצריך להבין את הפקודות בקובץ כמו ב-Scheme, ולא כמו ה-default של שפת ה-script. קובץ ה-script ,שנקרא גם הוא hello ,נראה כך:

":"; exec mzscheme -r $0 "$@"

(display "Hello, World!")
(newline))

כל דבר שבא אחרי השורה הראשונה הינו scheme תקני. בכל אופן, השורה הראשונה היא "הקסם" שהופכת זאת ל-script. כשהמשתמש מקיש hello ב-prompt של ה-Unix, Unix ייקרא את הקובץ כ-script רגיל. הדבר הראשון שהוא רואה זה: ":", שזו no-op עבור ה-Shell. ה-; הוא המפריד בין פקודות ב-shell. פקודת ה-Shell הבאה היא exec .exec אומרת ל-Unix לנטוש את ה-script הנוכחי ולהריץ במקומו את

mzscheme -r $0 "$@"

כאשר הפרמטר $0 יוחלף בשם ה-script, והפרמטר "@$" יוחלף ברשימת הארגומנטים שתינתן ע"י המשתמש ל-script. (במקרה שלנו, אין כאלו ארגומנטים).
למעשה, מה שעשינו עכשיו היה הפיכת פקודת ה-shell hello לפקודת shell אחרת. כלומר,

mzscheme -r /whereveritis/hello

כאשר

/whereveritis/hello 

הוא ה-pathname של hello.
mzscheme קוראת ל-MzScheme להתבצע. האופציה -r אומרת לטעון את הארגומנט העוקב כקובץ Scheme אחרי ששאר הארגומטים נכנסו לוקטור הנקרא argv. (במקרה זה argv יהיה וקטור ריק).
כך, ה-script ירוץ כקובץ Scheme, ולתבניות Scheme בקובץ תהיה גישה לארגומנטים המקוריים של ה-script דרך הוקטור argv.
כעת, על Scheme לטפל בשורה הראשונה ב-script, שכפי שכבר ראינו, אכן מסודרת היטב עבור ה-Shell המסורתי.
ה-":" היא מחרוזת ההערכה העצמית ולכן אינה מזיקה.
ה-";" מסמן הערה ב-Scheme וכמו כן מתעלמים מ-...exec.
שאר הקובץ הוא Scheme תקני, והביטויים בו מוערכים בצורה סדרתית. כאשר תסתיים ההערכה של כולם, Scheme תצא החוצה.
לסיכום, הקלדת hello ב-shell prompt תפיק !Hello, World ותחזיר אותנו ל-Shell prompt.



לתחילת העמוד

16.2 script עם ארגומנטים

script ב-scheme משתמש במשתנה argv כדי לפנות לארגומנטים שלו.
לדוגמא, ה-script הבא מציג את כל הארגומנטים שלו, כל אחד בשורה:

":"; exec mzscheme -r $0 "$@"

;Put in argv-count the number of arguments supplied

(define argv-count (vector-length argv))

    (let loop ((i 0))
         (unless (>= i argv-count) 
         (display (vector-ref argv i)) 
         (newline) 
         (loop (+ i 1))))

בואו ונקרא ל-script echoall .קריאה ל-echoall 1 2 3 תציג:
1
2
3
שים לב כי שם ה-script ("echoall") לא נכלל בוקטור הארגומנטים.



לתחילת העמוד

16.3 דוגמא

בואו נטפל כעת בבעיה רצינית יותר. אנו צריכים להעביר קבצים ממחשב אחד לשני והשיטה היחידה בה אנו צריכים להשתמש היא שימוש בדיסקט 3.5" כאמצעי העברה. אנו צריכים split4floppy script שיפצל קבצים גדולים מ-1.44 מליון בתים לנתחים שהם floppy-sized. ה-split4floppy script הוא כדלקמן:

":";exec mzscheme -r $0 "$@"

;floppy-size = number of bytes that will comfortably fit on a
;              3.5" floppy

(define floppy-size 1440000)

;split splits the bigfile f into the smaller, floppy-sized
;subfiles, viz, subfile-prefix.1, subfile-prefix.2, etc.

(define split
  (lambda (f subfile-prefix)
    (call-with-input-file f
      (lambda (i)
        (let loop ((n 1))
          (if (copy-floppy-size-chunk i subfile-prefix n)
              (loop (+ n 1))))))))

;copy-to-floppy-sized-subfile copies the next 1.44 million
;bytes (if there are less than that many bytes left, it
;copies all of them) from the big file to the nth
;subfile.  Returns true if there are bytes left over,
;otherwise returns false.

(define copy-to-floppy-sized-subfile
  (lambda (i subfile-prefix n)
    (let ((nth-subfile (string-append subfile-prefix "."
                                      (number->string n))))
      (if (file-exists? nth-subfile) (delete-file nth-subfile))
      (call-with-output-file nth-subfile
        (lambda (o)
          (let loop ((k 1))
            (let ((c (read-char i)))
              (cond ((eof-object? c) #f)
                    (else
                     (write-char c o)
                     (if (< k floppy-size)
                         (loop (+ k 1))
                         #t))))))))))

;bigfile = script's first arg
;        = the file that needs splitting

(define bigfile (vector-ref argv 0))

;subfile-prefix = script's second arg
;               = the basename of the subfiles

(define subfile-prefix (vector-ref argv 1))

;Call split, making subfile-prefix.{1,2,3,...} from
;bigfile

(split bigfile subfile-prefix)

הקריאה ל-split4floppy מתבצעת כך:

split4floppy largefile chuck

כך נפצל את largefile לתתי קבצים chunk.2 ,chunk.1 וכו' כך שכל subfile יהיה בגודל floppy.
אחרי העברת chunk.i למחשב היעד, הקובץ largefile יכול לחזור לקדמותו ע"י קשירת ה-chunk.i ביחד. דבר זה יכול להיעשות ב-Unix ע"י :

cat chunk.1 chunk.2 ... > largefile

וב-DOS ע"י:

copy /b chunk.1+chunk.2+... largefile