תהליכים

exec#

לנטוש את התוכנית הזו ולהריץ אחרת באותו תהליך.

exec מחליף את דמות התהליך הנוכחית בתוכנית אחרת. הוא אינו חוזר בהצלחה - הביצוע ממשיך בתוכנית החדשה, עם אותו process id, מטפלי קובץ פתוחים בכפוף לדגל close-on-exec, וכל סביבה שהייתה בתוקף. הוא מחזיר שקר (ומגדיר את $!) רק כאשר המטרה לא יכלה להיות מופעלת וגם היא בוצעה ישירות במקום דרך ה־shell. יש לפנות ל־system כאשר רוצים שהבן ירוץ והבקרה תחזור.

תקציר#

exec LIST
exec PROGRAM LIST
exec { PROGRAM } LIST

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

כלום, בהצלחה - התהליך הוחלף ומפרש Perl הזה נעלם. בכישלון exec מחזיר שקר ומשאיר את הסיבה ב־$!. כישלון ניתן לזיהוי רק במסלול ה־direct-exec; כאשר הארגומנט מועבר ל־shell, תוכנית חסרה מדווחת על־ידי ה־shell עצמו ו־exec עדיין נחשב כאילו ״הצליח״ בהפעלת /bin/sh.

exec '/usr/bin/vi', $file
    or die "couldn't exec vi: $!";

מכיוון ש־exec בדרך כלל אינו חוזר, Perl פולט אזהרה תחת use warnings אם הוא מופיע בהקשר void ואחריו הוראה אחרת שאינה die, warn, או exit. יש לטפל בכישלון inline עם or die, או לעטוף את הקריאה בבלוק כדי להשתיק את האזהרה:

exec('foo') or die "couldn't exec foo: $!";
{ exec('foo') }; die "couldn't exec foo: $!";

Shell או לא־shell - הכלל של מחרוזת יחידה#

exec בוחר בין שני מנגנוני הפעלה על בסיס הצורה של LIST:

  • שני ארגומנטים או יותרexec קורא ל־execvp(3) ישירות עם הרשימה. אין shell מעורב, אין פירוש מטה־תווים, אין פיצול מילים.

  • ארגומנט בודד בדיוקexec סורק את המחרוזת היחידה הזו לאיתור מטה־תווים של shell. אם נמצאים כאלה, המחרוזת כולה מועברת ל־/bin/sh -c לניתוח. אם אין, המחרוזת מפוצלת על רווחים ומועברת ל־execvp ישירות.

הכלל חשוב הן לנכונות והן לאבטחה. מחרוזת חד־ארגומנטית שמכילה רווח אך ללא מטה־תווים מפוצלת למילים ומבוצעת ישירות - וזה לרוב מה שרוצים. מחרוזת חד־ארגומנטית שמכילה |, >, $, ;, תווי glob, או מרכאות עוברת דרך ה־shell, וה־shell ירחיב בשמחה תווים כלליים, יפרש משתנים, ויפצל על $IFS.

exec '/bin/echo', 'Your arguments are:', @ARGV;  # direct execvp
exec "sort $outfile | uniq";                     # pipeline → shell
exec "ls /tmp";                                  # no metachars → split + execvp

כדי לאלץ את מסלול ה־no-shell גם עם פקודה לוגית בודדת, יש להשתמש בצורת ה־indirect-object שמתוארת להלן.

צורת indirect-object - להגדיר את argv[0], לדלג על ה־shell#

הצבת בלוק או סקלר לפני LIST (ללא פסיק) אומרת ל־exec שני דברים בו זמנית: להריץ את התוכנית הזו, ולהעביר את אותם ארגומנטים כ־argv. האלמנט הראשון של LIST הופך ל־argv[0] - השם שהבן רואה לעצמו - שאינו חייב להתאים לנתיב התוכנית.

exec { '/bin/echo' } 'echo', 'hi';   # argv[0] is "echo"
my $shell = '/bin/csh';
exec $shell '-sh';                   # login-shell convention: argv[0] = "-sh"
exec { '/bin/csh' } '-sh';           # same, more direct

צורת ה־indirect-object תמיד מתייחסת ל־LIST כרשימה רבת־ערכים, גם אם יש בה רק אלמנט אחד. זה משבית את סריקת המטה־תווים לחלוטין:

my @args = ('echo surprise');
exec @args;              # one element → shell scan → /bin/sh -c 'echo surprise'
exec { $args[0] } @args; # always direct execvp, "echo surprise" is argv[0]

בצורה השנייה אין תוכנית על הדיסק שנקראת באופן מילולי echo surprise, כך ש־execvp נכשל, exec מחזיר שקר, ו־$! מחזיק את הסיבה. זוהי ההתנהגות הבטוחה: הקורא מקבל את ההזדמנות להחליט מה לעשות, במקום שה־shell ירוץ משהו לא־צפוי.

מצב גלובלי שהוא נוגע בו#

  • $! - מוגדר בכישלון ל־errno מהקריאה הבסיסית execvp(3). משמעותי רק כאשר exec אכן חוזר.

  • $? - נשאר ללא שינוי על־ידי exec עצמו, אך ניסיון כושל בתיווך shell עשוי להשאיר את סטטוס היציאה של ה־shell נראה ל־system עוטף.

  • מטפלי הקובץ הפתוחים של התהליך, הסביבה (%ENV), ספריית העבודה הנוכחית, umask, סידורי אותות, ומגבלות משאבים - כולם עוברים לתוכנית המחליפה - exec מחליף את התוכנית, לא את התהליך.

דוגמאות#

להריץ תוכנית עם רשימת ארגומנטים מפורשת, execvp ישיר:

exec '/usr/bin/git', 'status', '--short'
    or die "exec git: $!";

להעביר שורת פקודה ל־shell במכוון, באמצעות מטה־תווים:

exec "find . -name '*.tmp' -print0 | xargs -0 rm"
    or die "exec pipeline: $!";

להגדיר argv[0] מובחן כך ששורת ה־ps של הבן מזהה את תפקידו:

exec { '/usr/local/bin/worker' } 'worker[queue=email]', @flags
    or die "exec worker: $!";

Fork-and-exec - התבנית הקלאסית להפעלת בן מבלי להחליף את עצמך:

my $pid = fork // die "fork: $!";
if ($pid == 0) {
    exec '/usr/bin/gzip', '-9', $file
        or die "exec gzip: $!";
}
waitpid $pid, 0;

להבטיח את מסלול ה־no-shell כאשר רשימת הארגומנטים הורכבה מנתונים:

my @cmd = build_command();           # may contain one element or many
exec { $cmd[0] } @cmd
    or die "exec $cmd[0]: $!";

מקרי קצה#

  • לעולם לא חוזר בהצלחה. כל קוד אחרי exec חשוף ניתן להגיע אליו רק בכישלון. תחת use warnings, הוראה עוקבת שאינה die, warn, או exit מפעילה אזהרה - רמז של Perl שכנראה התכוונת ל־system.

  • מלכודת ארגומנט בודד. exec $cmd כאשר $cmd הגיע מקלט משתמש הוא וקטור הזרקת shell אם $cmd מכיל מטה־תווים. יש להשתמש בצורת ה־indirect-object או להעביר רשימה מפוצלת מראש.

  • אגירת פלט. Perl מנסה לפלוט את התוכן של מטפלים שנפתחו לפלט לפני העברת הבקרה לתוכנית החדשה, אך זה לא מובטח בכל פלטפורמה. אם יש פלט ממתין על מטפל לא־מפוילט, יש להגדיר את $| על המטפל הנבחר - או לקרוא למתודה autoflush על כל IO::Handle - לפני exec.

  • בלוקי END ו־DESTROY. exec אינו מריץ בלוקי END, והוא אינו מפעיל DESTROY על העצמים שלך. דמות התהליך מוחלפת; ניקוי ברמת Perl לעולם לא קורה. כל מה שדורש סגירה נקייה (קבצים זמניים, הסרת lockfile, פליטת לוגים אגורים) חייב לקרות לפני exec.

  • Close-on-exec. מטפלי קובץ עם הדגל FD_CLOEXEC מוגדר נסגרים על־ידי הקרנל במהלך execvp; אחרים נשארים פתוחים. יש לשלוט בזה עם fcntl או עם סף מטפל הקובץ של המערכת $^F.

  • כישלון בתיווך shell אינו נראה. כאשר exec מנותב דרך /bin/sh -c, תוכנית מטרה חסרה מדווחת על־ידי ה־shell ו־exec עצמו כבר הוחלף על־ידי /bin/sh. קוד ה־Perl שלך לא יראה החזרת כישלון.

  • רשימת ארג בודד ללא מטה־תווים עדיין מפוצלת על רווחים ומועברת ל־execvp - אז exec "ls /tmp" הוא exec ישיר של /bin/ls עם ארגומנט אחד, לא קריאת shell.

  • LIST ריק היא שגיאת זמן ריצה; אין תוכנית להריץ.

הבדלים מ־upstream#

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

ראו גם#

  • system - להריץ תוכנית ולחכות לה; מה שכמעט בוודאות רצית אם exec נמצא בראש הקובץ

  • fork - לשייך עם exec בבן כדי להפעיל תוכנית מבלי להחליף את האב

  • wait - לאסוף בן שנוצר על־ידי fork + exec

  • die - לסיים את התוכנית הנוכחית עם שגיאה; השותף המקובל של exec ... or die

  • $! - סיבת השגיאה שמוגדרת כאשר direct-exec נכשל

  • $| - דגל autoflush; יש להגדיר לפני exec כדי שפלט אגור לא יאבד כאשר דמות התהליך מוחלפת