Είσοδος προγράμματος - @ARGV, %ENV, @INC#

Τρεις καθολικές μεταβλητές μεταφέρουν είσοδο στο πρόγραμμα: ορίσματα γραμμής εντολών, μεταβλητές περιβάλλοντος, και τη διαδρομή αναζήτησης αρθρωμάτων. Μοιράζονται αρκετό πλαίσιο ώστε οι αποφάσεις για μία συνήθως ενημερώνουν τις άλλες - η @ARGV αλληλεπιδρά με τη $0, τα κλειδιά σχετικά με Perl στη %ENV επηρεάζουν τη @INC, και το ερώτημα σειράς της @INC αντικατοπτρίζεται σε παρόμοιες ανησυχίες για καταναλωτές @ARGV και το PATH του κελύφους.

Μεταβλητή

Κρατά

@ARGV

Ορίσματα γραμμής εντολών (χωρίς το όνομα σεναρίου)

$0

Όνομα σεναρίου (σε ζεύγος με τη @ARGV)

%ENV

Περιβάλλον διεργασίας

@INC

Διαδρομή αναζήτησης αρθρωμάτων (χρησιμοποιείται από use/require)

%INC

Ήδη φορτωμένα αρθρώματα

$INC

Τρέχων δείκτης @INC κατά τη διάρκεια hook (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 | μετατρέπει το όρισμα σε open-από-σωλήνα-εντολής· αυτό είναι ένα παλιό ιδίωμα Perl για διαφανή αποσυμπίεση σε σενάρια τύπου awk.

%ENV - το περιβάλλον διεργασίας#

Η %ENV είναι το hash μεταβλητών περιβάλλοντος που κληρονομούνται από τη γονική διεργασία (τυπικά το κέλυφος). Η ανάγνωση είναι απλή:

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

Η αλλαγή δεν διαδίδεται πίσω στον γονέα - το κέλυφος που εκκίνησε το σενάριο κρατά το αρχικό του περιβάλλον. Αν χρειάζεστε το γονικό κέλυφος να πάρει νέες τιμές, γράψτε τες σε αρχείο ή ζητήστε από το κέλυφος να κάνει 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

Σπόρος τυχαιοποίησης hash ανά εκτέλεση

PERL_SIGNALS

unsafe για εξαίρεση από τον αναβαλλόμενο χειρισμό σημάτων

HOME

Διαβάζεται από την επέκταση glob ~· δεν είναι ειδική της Perl αλλά σχετική

PATH

Αναζητείται από system/exec για εντολές χωρίς πλήρη διαδρομή

Σε tainted mode (-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. Η μεταβλητή περιβάλλοντος PERL5LIBPERLLIB), επίσης στην αρχή.

Μετά την εκκίνηση είναι απλώς ένας κανονικός πίνακας. Η σειρά μετράει: μια αντιστοιχία σε πρώιμο στοιχείο υπερισχύει.

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 για «θέλω η διαδρομή αρθρωμάτων του κώδικά μου να ισχύει κατά τη μεταγλώττιση», κάτι που ισχύει σχεδόν σε κάθε περίπτωση.

Hooks @INC#

Η @INC μπορεί να περιέχει όχι μόνο διαδρομές αλλά και αναφορές κώδικα και blessed αντικείμενα. Όταν η require συναντά ένα από αυτά, το καλεί ως hook - περνώντας τη ζητούμενη διαδρομή αρθρώματος - και το hook επιστρέφει είτε ανοιχτό filehandle είτε λίστα πηγαίου κώδικα, γεννήτριες, κ.λπ. Έτσι μηχανισμοί όπως Module::Pluggable, PAR, και Test::MockModule υποκλέπτουν τη φόρτωση αρθρωμάτων.

Το πλήρες πρωτόκολλο hook τεκμηριώνεται στη require. Οι περισσότεροι χρήστες δεν γράφουν ποτέ ένα· τα διαβάζετε όταν αποσφαλματώνετε προβλήματα «γιατί αυτό το άρθρωμα δεν φορτώνεται από εκεί που περιμένω».

%INC - η cache φορτωμένων αρθρωμάτων#

Μετά την επιτυχή φόρτωση ενός αρθρώματος, η %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 βρίσκεται ήδη στη cache. Για εξαναγκασμό εκ νέου require:

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

Αυτό είναι το τυπικό ιδίωμα για δοκιμή συμπεριφοράς επαναφόρτωσης. Λάβετε υπόψη ότι η εκ νέου εκτέλεση αρχείου αρθρώματος δεν αναιρεί ό,τι δημιούργησε η πρώτη εκτέλεση - πακέτα, subs, και καθολικές μεταβλητές παραμένουν.

$INC - ο δείκτης μέσα σε hook @INC#

Διαθέσιμο από την Perl 5.37.7. Όταν καλείται ένα hook @INC, η $INC τίθεται στον δείκτη του hook στη @INC. Μετά την επιστροφή του hook, ο επαναλήπτης προχωρά βάσει $INC + 1 - οπότε το hook μπορεί να ξαναγράψει τη @INC και να κατευθύνει την αναζήτηση σε συγκεκριμένη θέση στη συνέχεια. Αυτό είναι προχωρημένο χαρακτηριστικό· ο καθημερινός κώδικας δεν το αγγίζει ποτέ.

Δείτε επίσης#

  • use, require - οι καταναλωτές της @INC/%INC.

  • use lib - η κανονική γραφή μεταγλώττισης για προσθήκη στην αρχή της @INC.

  • Getopt::Long - ο τυπικός αναλυτής για τη @ARGV.

  • readline - η μορφή συνάρτησης του τελεστή «διαμάντι» <> που κινεί το φιλτράρισμα @ARGV STDIN.

  • -i, -n, -p, -a - διακόπτες γραμμής εντολών χτισμένοι πάνω στη @ARGV.

  • Μεταβλητές διεργασίας - η $0 είναι το όνομα σεναρίου· η $$ είναι το PID· ζευγαρώστε τα με τη @ARGV για αυτο-επανεκκίνηση και καταγραφή.

  • Μονόγραμμα γραμμής εντολών - ο οδηγός που δείχνει κάθε συνδυασμό @ARGV/<>/διακόπτη σε πλαίσιο.