קלט תוכנית - @ARGV, %ENV, @INC#

שלושה גלובליים נושאים קלט אל התוכנית: ארגומנטים של שורת הפקודה, משתני סביבה, ונתיב חיפוש המודולים. הם חולקים מספיק הקשר כך שהחלטות לגבי אחד בדרך כלל מיידעות את האחרים - @ARGV מקיים אינטראקציה עם $0, המפתחות הרלוונטיים־ל־Perl של %ENV משפיעים על @INC, ושאלת הסדר של @INC משתקפת בדאגות דומות עבור צרכני @ARGV ו־PATH של ה־shell.

משתנה

מחזיק

@ARGV

ארגומנטים של שורת הפקודה (למעט שם הסקריפט)

$0

שם הסקריפט (מזווג עם @ARGV)

%ENV

סביבת התהליך

@INC

נתיב חיפוש מודולים (משמש את use/require)

%INC

מודולים שכבר נטענו

$INC

אינדקס @INC נוכחי במהלך הוו (5.37.7+)

@ARGV ויחסו ל־$0#

לאחר ש־Perl מתחיל, שם הסקריפט נמצא ב־$0 וארגומנטי הסקריפט נמצאים ב־@ARGV:

$ pperl myscript.pl -v --output /tmp/x foo bar
print "$0\n";                    # myscript.pl
print "@ARGV\n";                 # -v --output /tmp/x foo bar
print scalar(@ARGV), " args\n";  # 5 args

$ARGV[0] הוא הארגומנט הראשון של הסקריפט, לא שם הסקריפט עצמו. זה שונה מ־argv של C (שבו argv[0] הוא שם התוכנית); Perl חושף את שם התוכנית בנפרד דרך $0.

טיפול תקני בארגומנטים#

פענוח ארגומנטים בכתב יד תקין עבור סקריפטים טריוויאליים:

my %opt;
while (@ARGV && $ARGV[0] =~ /^-/) {
    my $flag = shift @ARGV;
    last if $flag eq '--';
    if    ($flag eq '-v')      { $opt{verbose} = 1 }
    elsif ($flag eq '--output'){ $opt{output}  = shift @ARGV }
    else                       { die "unknown flag: $flag\n" }
}
my @files = @ARGV;

לכל דבר מעבר לסקריפטי צעצוע, המודול Getopt::Long מטפל בכל תבנית תקנית (אופציות ארוכות, קיצור, ארגומנטים חובה, דגלים חוזרים, מפריד --, עזרה אוטומטית):

use Getopt::Long;
my %opt;
GetOptions(
    'verbose|v'    => \$opt{verbose},
    'output|o=s'   => \$opt{output},
    'help|h'       => \$opt{help},
) or die "bad options; try --help\n";
my @files = @ARGV;               # what's left after option processing

GetOptions משנה את @ARGV במקום - כשהוא חוזר, @ARGV מכיל רק את הארגומנטים שאינם אופציות.

@ARGV ואופרטור היהלום#

האופרטור <> (יהלום) מבצע איטרציה על שורות מהקבצים הנקובים ב־@ARGV, ונופל בחזרה ל־STDIN אם @ARGV ריק:

while (<>) {
    # one line from one of the @ARGV files (or STDIN)
    chomp;
    process($_);
}

זהו החוזה ברירת המחדל של כל תוכנית סינון בסגנון awk. <> פותח כל קובץ בתורו, קובע את $ARGV לשמו במהלך הקריאה, וסוגר אותו ב־EOF.

-i ושאר מתגי ה־- (-n, -p, -l, -a) כולם בונים את התנהגותם על תבנית @ARGV <> זו.

שינוי @ARGV לפני <>#

ניתן לעבד מראש את @ARGV כדי לשנות את מה ש־<> קורא:

# Add an extra file to the front of the argv list:
unshift @ARGV, 'header.txt';

# Process compressed files transparently by rewriting argv:
@ARGV = map { /\.gz$/ ? "gunzip -c $_ |" : $_ } @ARGV;

while (<>) {
    process($_);
}

הצורה cmd | הופכת את הארגומנט לפתיחת pipe־מפקודה; זהו ניב Perl ותיק עבור דחיסה־מבוטלת שקופה בסקריפטים בסגנון awk.

%ENV - סביבת התהליך#

%ENV הוא האש משתני הסביבה שנירשו מתהליך ההורה (בדרך כלל ה־shell). הקריאה פשוטה:

my $home    = $ENV{HOME};
my $term    = $ENV{TERM} // 'dumb';
my $path    = $ENV{PATH};

כתיבה משנה את הסביבה שנראית על ידי תהליכי ילד שסקריפט Perl זה מוליד לאחר מכן:

$ENV{LC_ALL}     = 'C';          # POSIX locale for child commands
$ENV{TZ}         = 'UTC';
$ENV{LANG}       = 'C';
delete $ENV{LC_NUMERIC};         # remove a key entirely

system('date');                   # child sees the modified ENV

השינוי אינו מתפשט בחזרה אל ההורה - ה־shell שהתחיל את הסקריפט שומר על סביבתו המקורית. אם אתם זקוקים לכך שה־shell ההורה יקלוט ערכים חדשים, כתבו אותם לקובץ או גרמו ל־shell לבצע eval על הפלט שלכם (תבנית ה־dotenv התקנית).

המרת ערכים למחרוזת#

החל מ־Perl 5.18, ערכי %ENV תמיד מומרים למחרוזת בזמן ההשמה. אחסון הפניה כבר אינו עושה נסיעה חזור:

my $arr = [1, 2, 3];
$ENV{DATA} = $arr;
print $ENV{DATA};                # "ARRAY(0x...)" - stringified

משתני סביבה הם ממשק מטיפוס מחרוזת אל מערכת ההפעלה; שמירת מבנה על פני גבול fork/exec לא הייתה עובדת בכל מקרה. ארזו כל דבר לא־טריוויאלי כ־JSON או דומה לפני האחסון.

משתני סביבה ייחודיים ל־Perl#

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

משתנה

השפעה

PERL5LIB

נתיבים מופרדים בנקודתיים שמוקדמים ל־@INC (כפופים לכללי taint)

PERLLIB

אותו דבר, משתנה ישן יותר; משמש רק אם PERL5LIB אינו מוגדר

PERL5OPT

מתגים שמיושמים כאילו בשורת הפקודה (תת־קבוצה בלבד)

PERLDB_OPTS

אופציות עבור מנפה השגיאות

PERL_UNICODE

הגדרות שכבת UTF-8 ברירת מחדל (משקפות את -C)

PERL_USE_UNSAFE_INC

הוספה מחדש של . ל־@INC (הוסר כברירת מחדל מאז 5.26 - לא מומלץ בחום)

PERL_HASH_SEED

זרע אקראיות האש לכל־ריצה

PERL_SIGNALS

unsafe כדי לבטל את ההצטרפות לטיפול אותות דחוי

HOME

נקרא על ידי הרחבת glob של ~; אינו ייחודי ל־Perl אך רלוונטי ל־Perl

PATH

נחפש על ידי system/exec עבור פקודות לא־מוסמכות

במצב taint (-T), PERL5LIB, PERL5OPT, ו־PERLLIB מתעלמים מהם כדי למנוע הסלמת הרשאות. ראו את מדריך מתגי שורת הפקודה עבור מודל האבטחה המלא.

delete $ENV{KEY} מול $ENV{KEY} = undef#

השניים אינם שקולים:

delete $ENV{KEY};                # KEY is not in the environment
$ENV{KEY} = undef;               # KEY is in the environment, value ""

A child process started after delete will not see KEY at all

  • getenv("KEY") returns NULL. After undef assignment, the child sees KEY="" (empty string). Code that distinguishes ”unset“ from ”set to empty“ cares about this difference.

@INC - נתיב חיפוש המודולים#

כאשר use ו־require מחפשים מודול, הם עוברים על @INC משמאל לימין ועוצרים בהתאמה הראשונה:

print "$_\n" for @INC;
# /usr/local/lib/perl5/site_perl/5.42.0/x86_64-linux
# /usr/local/lib/perl5/site_perl/5.42.0
# /usr/local/lib/perl5/5.42.0/x86_64-linux
# /usr/local/lib/perl5/5.42.0
# (no trailing "." since Perl 5.26)

@INC מאותחל בהפעלה משילוב של:

  1. ברירות מחדל מהודרות־פנימה (ה־installprivlib, installsitelib, … מזמן הבנייה).

  2. מתגי -I /path בשורת הפקודה, מוקדמים בסדר.

  3. משתנה הסביבה PERL5LIB (או PERLLIB), גם הוא מוקדם.

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

use lib מול unshift @INC מול push @INC#

שלוש דרכים להוסיף נתיב. הן אינן שקולות:

use lib '/my/lib';                       # unshift at compile-time
unshift @INC, '/my/lib';                 # unshift at runtime
push    @INC, '/my/lib';                 # push    at runtime

ההבדלים:

  • use lib רץ בזמן הידור. הוא קורה לפני כל הוראת use שבאה אחריו בעמוד. זה מה שאתם כמעט תמיד רוצים כשאתם אומרים ”סקריפט זה זקוק לספריית ספרייה מותאמת“:

    use lib '/opt/myapp/lib';
    use MyApp::Module;                     # found because lib was unshifted first
    

    פנימית use lib '/path' מבצע BEGIN { unshift @INC, '/path' }, ולכן הנתיב החדש נחפש ראשון - עותק של מודול ב־/opt/myapp/lib מסתיר אחד ב־@INC של המערכת.

  • unshift @INC, '/path' ברמה העליונה בעל סמנטיקת זמן ריצה. הוא עובד עבור קריאות require שקורות לאחר ה־unshift, אך הוראת use בשורה מאוחרת יותר של אותו קובץ לא תראה אותו (משום ש־use עצמו הוא בזמן הידור):

    unshift @INC, '/my/lib';
    use Some::Module;                      # NOT found in /my/lib - too early
    

    זוהי מלכודת תכופה. BEGIN { unshift @INC, '/my/lib' } מתקן זאת; use lib הוא הכתיב הנקי יותר של בדיוק זה.

  • push @INC, '/path' appends. The new path is searched last. This is the right choice when you want your library to be a fallback - used only if no other location has the module

    • for example, providing a built-in version of a CPAN module that the user might have installed on the system.

כלל אצבע: תמיד השתמשו ב־use lib עבור ”אני רוצה שנתיב המודול של הקוד שלי ייכנס לתוקף בזמן הידור“, שזה כמעט כל מקרה.

הוּוי @INC#

@INC עשוי להכיל לא רק נתיבים אלא גם הפניות קוד ועצמים blessed. כאשר require נתקל באחד מאלה, הוא קורא לו כהוו - מעביר את נתיב המודול המבוקש - וההוו מחזיר או מטפל קובץ פתוח או רשימה של קוד מקור, מחוללים, וכו«. כך מנגנונים כמו Module::Pluggable, PAR, ו־Test::MockModule מיירטים את טעינת המודולים.

פרוטוקול ההוו המלא מתועד תחת require. רוב המשתמשים לעולם אינם כותבים אחד; אתם קוראים עליהם בעת ניפוי בעיות מסוג ”מדוע מודול זה אינו נטען מהמקום שאני מצפה“.

%INC - מטמון המודולים הטעונים#

לאחר שמודול נטען בהצלחה, %INC רושם אותו. המפתח הוא הנתיב ש־Perl התבקש למצוא (למשל Foo/Bar.pm); הערך הוא הנתיב המוחלט שסיפק את הבקשה:

use Data::Dumper;
print "Data::Dumper loaded from $INC{'Data/Dumper.pm'}\n";
# /usr/local/lib/perl5/site_perl/5.42.0/Data/Dumper.pm

# Show every loaded module:
for my $key (sort keys %INC) {
    print "$key  →  $INC{$key}\n";
}

require בודק את %INC לפני שהוא עובר על @INC - require Foo; שני הוא no-op משום ש־Foo.pm כבר במטמון. כדי לכפות require מחדש:

delete $INC{'Foo.pm'};
require Foo;                     # actually re-runs Foo.pm

זהו הניב התקני לבדיקת התנהגות טעינה מחדש. שימו לב שהרצה מחדש של קובץ מודול אינה מבטלת את הגדרת מה שהריצה הראשונה יצרה - חבילות, תת־שגרות, וגלובליים נשארים.

$INC - האינדקס בתוך הוו @INC#

זמין מאז Perl 5.37.7. כאשר הוו @INC נקרא, $INC נקבע לאינדקס של ההוו ב־@INC. לאחר שההוו חוזר, האיטרטור מתקדם על בסיס $INC + 1 - כך שההוו יכול לכתוב מחדש את @INC ולכוון את החיפוש למיקום ספציפי לאחר מכן. זוהי תכונה מתקדמת; קוד יומיומי לעולם אינו נוגע בה.

ראו גם#

  • use, require - הצרכנים של @INC/%INC.

  • use lib - הכתיב הקנוני בזמן הידור עבור הקדמה ל־@INC.

  • Getopt::Long - הפענח התקני עבור @ARGV.

  • readline - צורת הפונקציה של אופרטור היהלום <> שמניע את הסינון @ARGV STDIN.

  • -i, -n, -p, -a - מתגי שורת פקודה הבנויים על @ARGV.

  • משתני תהליך - $0 הוא שם הסקריפט; $$ הוא ה־PID; זווגו אותם עם @ARGV עבור הפעלה־מחדש־עצמית ורישום.

  • שורות־פקודה חד־שורתיות - ההדרכה המציגה כל שילוב @ARGV/<>/מתג בהקשר.