זרימת בקרה

exit#

לסיים את התוכנית עם ערך סטטוס.

exit מעריך את EXPR, מריץ ניקוי סוף־התוכנית (בלוקי END והורסי עצמים), ואז יוצא מהתהליך עם הערך הזה כקוד הסטטוס שלו. ללא ארגומנט הוא יוצא 0 - הצלחה.

תקציר#

exit EXPR
exit

מה מקבלים בחזרה#

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

exit 1;
print "never\n";            # dead code

ערכי סטטוס וקיצוץ ל־8 ביט#

מערכת ההפעלה מאחסנת סטטוס יציאה ב־8 ביט. כל מספר שלם שמועבר ל־exit נלקח מודולו 256 על־ידי הקרנל לפני שתהליך האב רואה אותו. exit 256 נראה כמו exit 0 ל־shell. exit -1 הופך ל־255. יש להעביר רק ערכים בטווח 0..255 ובכך להימנע מההפתעה.

הערכים היחידים שהם ניידים אוניברסלית הם 0 להצלחה ו־1 לשגיאה גנרית. למספרים אחרים יש מוסכמות - הטווח של sysexits.h 64..78 ב־Unix, קודים ספציפיים שמסנני sendmail מצפים להם, וכן הלאה - אבל אלה הסכמים בין התוכנית שלך לקורא ספציפי, לא ערבויות של השפה. יציאה עם 69 (EX_UNAVAILABLE) אומרת למסנן דואר־נכנס של sendmail לדחות את ההודעה; אותו 69 מסקריפט שמורץ על־ידי cron לא אומר משהו ספציפי.

exit 0;                     # success
exit 1;                     # generic failure
exit 2;                     # conventional "misuse" (grep, diff)
exit 77;                    # conventional "skip" (Automake test)

ניקוי: בלוקי END והורסים#

exit אינה קריאת מערכת מיידית. לפני שהתהליך נעלם, Perl:

  1. מריץ כל בלוק END בסדר הפוך של הידור (LIFO).

  2. קורא ל־DESTROY על כל עצם שספירת ההפניות שלו יורדת לאפס כאשר ההריסה הגלובלית מפרקת pads, את טבלת הסמלים, וגלובליים של חבילות.

שני סוגי הניקוי יכולים לצפות ולשנות את $?. הערך שהעברת ל־exit ממוקם ב־$? בכניסה לשלב END/DESTROY; כל מה ש־$? מחזיק כאשר הניקוי האחרון חוזר הוא הסטטוס שאיתו התהליך יוצא.

END { $? = 0 if $? == 2; }  # rewrite "misuse" to success

exit 2;                     # process actually exits 0

זה לפעמים שימושי ותמיד מפתיע. אם מודול שלא כתבת מתקין בלוק END שנוגע ב־$?, קוד הסטטוס שבחרת בקפידה יכול להגיע ל־shell ההורה משונה.

כאשר נדרשת יציאה קשה, ללא תנאי, שעוקפת את END ו־DESTROY לחלוטין, יש לקרוא ל־POSIX::_exit:

use POSIX ();
POSIX::_exit(3);            # no END, no DESTROY, status = 3

יש לפנות ל־_exit בתהליכי בן אחרי fork כאשר האב הוא הבעלים של הניקוי, ובמטפלי אותות כאשר הרצת קוד Perl שרירותי אינה בטוחה.

exit אינו die#

exit הוא הכלי הלא־נכון לדיווח על שגיאה מספריה או מתת־שגרה. קורא גבוה יותר במחסנית עשוי לרצות לתפוס את הכישלון, לרשום ביומן, ולהמשיך - בלוק eval או עטיפת Try::Tiny אינם יכולים להתאושש מ־exit. התהליך נעלם.

sub load_config {
    my ($path) = @_;
    open my $fh, "<", $path
        or die "open $path: $!";   # correct - trappable
    ...
}

# NOT:
# open my $fh, "<", $path or exit 1;

יש להשתמש ב־die לתנאי שגיאה. יש לשמור את exit לרמה העליונה של התוכנית שבה הוחלט, כיישום, שההרצה הסתיימה.

exit בתוך eval#

exit אינו נתפס על־ידי eval. שלא כמו die, שמפרק עד ל־eval הקרוב ביותר ומגדיר את $@, exit עובר היישר מעבר לכל מסגרת eval במחסנית, מריץ END/DESTROY, ומסיים.

eval { exit 5 };            # process exits 5; the eval block
print "after eval\n";       # never runs

אם כותבים מארח plugin או רתמת בדיקות שחייבת לשרוד קוד משתמש שמתנהג בצורה לא־ראויה וקורא ל־exit, אין catch ברמת השפה. הפתרון המקובל הוא לבצע fork, לתת לבן לקרוא ל־exit, ולבחון את הסטטוס של הבן באב:

my $pid = fork // die "fork: $!";
if ($pid == 0) {
    run_user_code();        # may call exit
    exit 0;
}
waitpid $pid, 0;
my $status = $? >> 8;

דוגמאות#

חזרה מוקדמת מהתוכנית אחרי שאלה:

my $ans = <STDIN>;
exit 0 if $ans =~ /^[Xx]/;

יציאה עם סטטוס שנגזר מתהליך בן - לחלץ את הבית הגבוה של $? כך שה־shell יראה את אותו קוד שהבן ייצר:

system @cmd;
exit $? >> 8 if $?;

לשכתב סטטוס בבלוק END - כל מה ש־$? מחזיק בסוף הניקוי הוא סטטוס היציאה שמועבר לאב:

END {
    $? = 0 if $? == 2 && $ENV{IGNORE_MISUSE};
}

לעקוף ניקוי בבן שעבר fork כדי שההורסים של האב לא ירוצו פעמיים:

use POSIX ();
my $pid = fork // die "fork: $!";
if ($pid == 0) {
    exec @cmd;
    POSIX::_exit(127);      # exec failed; do NOT run parent's END
}

מקרי קצה#

  • exit ללא ארגומנט זהה ל־exit 0. סקריפטים רבים מסתמכים על נפילה מסוף הקובץ כדי לקבל 0 משתמע; כתיבת exit; הופכת את הכוונה למפורשת.

  • ארגומנט שאינו מספר שלם: EXPR מוערך בהקשר סקלרי ומומר למספר שלם. exit "3x" יוצא 3; exit "hello" יוצא 0 עם אזהרה תחת use warnings.

  • ערכים שליליים נלקחים מודולו 256 על־ידי הקרנל. exit -1 נראה ל־shell כ־255. יש להעביר רק 0..255 כדי להימנע מהמרה.

  • ערכים מעל 255 מאבדים את הביטים הגבוהים שלהם. exit 256 נראה כמו הצלחה; exit 300 נראה כמו exit 44. אם נדרש להעביר ערך גדול יותר, יש לכתוב אותו לקובץ או ל־STDERR ולצאת עם קוד סטטוס קטן.

  • בלוקי END יכולים לבטל את הסטטוס שלך אך לא את היציאה עצמה. בלוק END אינו יכול לעצור את התהליך מלהסתיים - הוא יכול רק לשנות את $?, ולכן את הסטטוס שהאב רואה.

  • סדר ההריסה הגלובלית אינו ההפוך של הבנייה. במהלך הסריקה הסופית Perl עובר על גלובליים של חבילות בסדר שמוגדר על־ידי המימוש. אין להסתמך על כך ש־DESTROY אחד ירוץ לפני אחר; אם הסדר חשוב, יש לפרק את העצמים במפורש לפני קריאה ל־exit.

  • בתוך בלוק BEGIN, exit מסיים את שלב ההידור ויוצא מהתהליך. בלוקי END שכבר נרשמו רצים; בלוקי BEGIN/END/קוד מאוחרים יותר בקובץ אינם.

  • Threads: בתוכנית עם מספר thread של מפרש Perl, exit מסיים את כל התהליך, לא רק את ה־thread הקורא. יש להשתמש ב־threads->exit ליציאה פר־thread.

הבדלים מ־upstream#

תואם מלא ל־upstream Perl 5.42.

ראו גם#

  • die - להעלות חריגה הניתנת ללכידה; הבחירה הנכונה לדיווח על שגיאות מתת־שגרה

  • warn - לפלוט אבחנה מבלי לסיים; משתייך ל־die באופן שבו print משתייך ל־exit

  • בלוקי END - קוד ניקוי שנרשם בזמן הידור שרץ בסדר LIFO בכל יציאה, כולל exit EXPR

  • מתודות DESTROY - ניקוי פר־עצם שנקרא במהלך הריסה גלובלית לאחר שבלוקי END רצו

  • $? - סטטוס יציאה של תהליך בן / סופי; נראה ובר־שינוי על־ידי בלוקי END והורסים

  • POSIX::_exit - סיום מיידי שעוקף את END ו־DESTROY; יש להשתמש בו בבני־תהליך אחרי fork ובמטפלי אותות