eval#
Εκτελεί ένα κομμάτι κώδικα Perl με τυχόν μοιραίο σφάλμα να παγιδεύεται αντί να σκοτώνει το πρόγραμμα.
Η eval είναι ταυτόχρονα ο μηχανισμός πιασίματος εξαιρέσεων της Perl και ο φορτωτής κώδικα κατά τον χρόνο εκτέλεσης. Έρχεται σε δύο πραγματικές μορφές: eval μπλοκ (eval { ... }), που πιάνει εξαιρέσεις από ήδη μεταγλωττισμένο κώδικα, και eval συμβολοσειράς (eval EXPR), που μεταγλωττίζει και εκτελεί μια συμβολοσειρά πηγαίου κώδικα Perl σε χρόνο εκτέλεσης. Όπως και να έχει, μια die που διαφορετικά θα τερμάτιζε το πρόγραμμα γίνεται ανάθεση στην $@ και κανονική επιστροφή.
Σύνοψη#
eval BLOCK # catch exceptions in compiled code
eval EXPR # compile and run source at runtime
eval # short for: eval $_
Τι επιστρέφεται#
Η τιμή της τελευταίας έκφρασης που αποτιμήθηκε μέσα στο μίνι-πρόγραμμα, ή η τιμή που παραδόθηκε σε ρητή return. Η αποτίμηση γίνεται στο ίδιο περιβάλλον με την περιβάλλουσα eval - κενό, βαθμωτό ή λίστας - οπότε η wantarray μέσα σε eval αναφέρει το περιβάλλον του καλούντος, όχι «κάποιο περιβάλλον eval».
Αν κάτι μέσα εγείρει εξαίρεση (die, διαίρεση δια του μηδενός, συντακτικό σφάλμα σε eval συμβολοσειράς, οποιοδήποτε άλλο παγιδεύσιμο μοιραίο), η eval επιστρέφει undef σε βαθμωτό περιβάλλον και την κενή λίστα σε περιβάλλον λίστας, και η εξαίρεση προσγειώνεται στην $@:
my $n = eval { risky() }; # undef on failure, $@ holds the error
if ($@) { ... }
Σε επιτυχία, η $@ τίθεται στην κενή συμβολοσειρά. Κάθε ολοκληρωμένη eval αγγίζει την $@ - δεν υπάρχει επιλογή «αφήστε τη ήσυχη». Ένας τελεστής ροής ελέγχου που παρακάμπτει την κανονική έξοδο (last, goto έξω από το μπλοκ) μπορεί να αφήσει την $@ αμετάβλητη, που είναι οριακή περίπτωση, όχι χαρακτηριστικό στο οποίο να βασίζεστε.
Καθολική κατάσταση που επηρεάζει#
$@- εγγράφεται σε κάθε κανονική έξοδο τηςeval: η εκτοξευμένη εξαίρεση σε αποτυχία, η κενή συμβολοσειρά σε επιτυχία. Διαβάζεται για να καθοριστεί αν ηevalέπιασε κάτι.$^S- αντικατοπτρίζει αν η Perl βρίσκεται αυτή τη στιγμή μέσα σεeval(ήtry).undefκατά την ανάλυση, αληθής κατά την εκτέλεση μέσα σε μπλοκeval/try, ψευδής στο ανώτατο επίπεδο. Διαβάζεται κυρίως από χειριστές$SIG{__DIE__}που θέλουν να ενεργήσουν διαφορετικά για πιασμένες έναντι μη πιασμένων εξαιρέσεων.$SIG{__DIE__}- πυροδοτείται σε κάθεdie, συμπεριλαμβανομένων εκείνων που μια περικλείουσαevalπρόκειται να πιάσει. Χρησιμοποιήστεlocal $SIG{__DIE__}μέσα σεevalγια να καταστείλετε έναν καθολικά εγκαταστημένο γάντζο για τη διάρκεια της παγίδας.$SIG{__WARN__}- δεν επηρεάζεται από τηνeval. Οι προειδοποιήσεις που εκπέμπονται μέσα στο μπλοκ εξακολουθούν να ακολουθούν την κανονική διαδρομή προειδοποίησης· ηevalδεν τις ανακατευθύνει στην$@.$_- η πηγή για τη μορφή χωρίς όρισμα (evalχωρίς τίποτα μετά από αυτή είναι ακριβώςeval $_).
eval μπλοκ έναντι eval συμβολοσειράς#
Οι δύο μορφές μοιάζουν παρόμοιες αλλά κάνουν πολύ διαφορετικά πράγματα.
eval μπλοκ (
eval { ... }) αναλύεται και μεταγλωττίζεται μαζί με το περιβάλλον πρόγραμμα. Σε χρόνο εκτέλεσης είναι απλώς ένα σήμα που λέει «πιάσε όποιαdieεκτοξευθεί από μέσα από αυτό το μπλοκ». Επειδή ο κώδικας είναι ήδη μεταγλωττισμένος, η eval μπλοκ είναι φθηνή και ασφαλής - ο μεταγλωττιστής τον έχει ήδη ελέγξει για συντακτικά σφάλματα. Αυτή είναι η μορφή στην οποία πρέπει να καταφεύγετε όποτε ο στόχος είναι ο χειρισμός εξαιρέσεων.eval συμβολοσειράς (
eval EXPR) αποτιμά τηνEXPRσε βαθμωτό περιβάλλον, στη συνέχεια αναλύει και εκτελεί την προκύπτουσα συμβολοσειρά σαν να ήταν μπλοκ στη λεκτική εμβέλεια του περιβάλλοντος κώδικα. Η ανάλυση γίνεται κάθε φορά που προσεγγίζεται ηeval. Ένα συντακτικό σφάλμα στη συμβολοσειρά γίνεται$@σε χρόνο εκτέλεσης, όχι αποτυχία κατά τη μεταγλώττιση του περιβάλλοντος προγράμματος. Αυτή είναι η μορφή για τη φόρτωση κώδικα που αποφασίζεται σε χρόνο εκτέλεσης - προαιρετικά χαρακτηριστικά, εκφράσεις παρεχόμενες από τον χρήστη, δυναμικά κατασκευασμένες υπορουτίνες - και κουβαλά τα κόστη και τους κινδύνους που αυτό συνεπάγεται.
Και οι δύο μορφές εκτελούνται στη λεκτική εμβέλεια του περιβάλλοντος κώδικα: οι εξωτερικές μεταβλητές my είναι ορατές, και αναθέσεις σε μεταβλητές πακέτου, ορισμοί υπορουτινών και ορισμοί φορμάτ που γίνονται μέσα στην eval παραμένουν αφού αυτή επιστρέψει.
Το συμβόλαιο της $@#
Μετά την κανονική ολοκλήρωση μιας eval, η $@ είναι πάντα ένα από:
Η κενή συμβολοσειρά - το μπλοκ έτρεξε μέχρι το τέλος χωρίς εξαίρεση.
Συμβολοσειρά που τελειώνει σε newline -
die "message\n"ή συμβολοσειριακή εξαίρεση που ο καλούμενος έχει ήδη τερματίσει.Συμβολοσειρά με προσαρτημένη τοποθεσία (
... at FILE line N.\n) -die "message"χωρίς τελικό newline, ή σφάλμα χρόνου εκτέλεσης που εγείρει η ίδια η Perl.Μια αναφορά, τυπικά ένα blessed αντικείμενο εξαίρεσης - η
dieπου κλήθηκε με αναφορά.
Ελέγξτε την $@ αμέσως μετά την eval και πουθενά αλλού. Οποιαδήποτε κλήση μεθόδου, έλεγχος ref, έλεγχος isa ή ταίριασμα regex κατά τον χειρισμό μπορεί να καλέσει η ίδια εσωτερικά μια eval (για παράδειγμα κατά την autoload ή την επίλυση υπερφόρτωσης) και να γράψει πάνω στην $@ πριν τελειώσετε την επιθεώρησή της. Αντιγράψτε πρώτα σε λεκτική μεταβλητή:
eval { work() };
if (my $err = $@) { # safe snapshot
if (ref $err && $err->isa('MyApp::Error')) { handle($err) }
else { die $err }
}
Εμφωλευμένη eval και local $@#
Η εξωτερική eval βλέπει ό,τι κρατά η $@ όταν τελειώσει το εξωτερικό μπλοκ - όχι ό,τι κρατούσε στο ενδιάμεσο. Αν ο εσωτερικός κώδικας έχει δική του eval, αυτή η εσωτερική eval θέτει την $@ (στην κενή συμβολοσειρά σε επιτυχία), που μπορεί να αλλοιώσει ένα σφάλμα που ο εξωτερικός κώδικας ήθελε να διαφυλάξει. Η προστασία είναι local $@ γύρω από την εσωτερική εργασία:
eval {
# ... some work that might fail ...
{
local $@; # shield outer $@
eval { optional_step() };
# ignore whatever optional_step did to $@
}
die "still needed\n" if $condition;
};
warn "outer caught: $@" if $@;
Πριν την Perl 5.14, η $@ ανατίθετο πριν αποκατασταθούν οι τοπικοποιημένες μεταβλητές, οπότε κώδικας που ήθελε να φιλτράρει σφάλματα έπρεπε να αναμεταδίδει την τιμή μέσω προσωρινής μεταβλητής. Στην 5.14+ (την οποία κληρονομεί η 5.42) η σειρά είναι διορθωμένη και η προσωρινή μεταβλητή είναι περιττή.
Ασφάλεια της eval συμβολοσειράς#
Η eval συμβολοσειράς εκτελεί ό,τι βρίσκεται στην έκφραση. Αν οποιοδήποτε μέρος αυτής της συμβολοσειράς προήλθε από μη έμπιστη είσοδο - αρχείο, δικτυακό άκρο, φόρμα χρήστη - η eval παραδίδει στον επιτιθέμενο τον πλήρη διερμηνέα Perl. Πρακτικοί κανόνες:
Μη διαβιβάζετε ποτέ είσοδο χρήστη μέσω
eval EXPRατροποποίητη. «Είναι μόνο αριθμός» δεν αποτελεί δικλείδα - και το"1; system 'rm -rf /'"είναι μόνο μια συμβολοσειρά.Αν χρειάζεται πραγματικά κώδικας επιλεγμένος σε χρόνο εκτέλεσης, περιορίστε την είσοδο έναντι μιας αυστηρής λίστας επιτρεπτών πριν την παρεμβολή, και προτιμήστε καθοδηγούμενη από δεδομένα διανομή (hash από coderefs) έναντι παραγωγής πηγαίου κώδικα.
Τιμές κινητής υποδιαστολής που παρεμβάλλονται σε πηγαίο κώδικα είναι εύθραυστες. Υπό
use localeο διαχωριστής δεκαδικών μπορεί να μην είναι τελεία, και τιμές όπως"NaN"ή"Infinity"μετατρέπονται σε συμβολοσειρές με γράμματα που ο αναλυτής διαβάζει ως barewords, όχι αριθμούς.Φίλτρα πηγαίου κώδικα που ενεργοποιούνται μέσα σε eval συμβολοσειράς διαρρέουν στην περιβάλλουσα εμβέλεια αρχείου (εκτός αν χρησιμοποιηθεί η
evalbytes). Αυτή είναι μακροχρόνια ιδιοτροπία, όχι χαρακτηριστικό.
Η eval μπλοκ δεν έχει κανένα από αυτά τα προβλήματα - ο κώδικας μεταγλωττίστηκε μαζί με το υπόλοιπο πρόγραμμα.
BEGIN μέσα σε eval συμβολοσειράς#
Μπλοκ BEGIN { ... } ενσωματωμένα σε eval συμβολοσειράς εκτελούνται αμέσως, πριν τρέξει ο υπόλοιπος κώδικας της eval, ταιριάζοντας τη συμπεριφορά τους σε μια κανονική μονάδα μεταγλώττισης. Θέστε την ${^MAX_NESTED_EVAL_BEGIN_BLOCKS} σε 0 ώστε όποιο ενσωματωμένο BEGIN να εκτοξεύει εξαίρεση αντί να εκτελείται:
local ${^MAX_NESTED_EVAL_BEGIN_BLOCKS} = 0;
eval $untrusted; # any BEGIN inside $untrusted dies
Αυτό δεν κάνει ασφαλή την eval συμβολοσειράς - απλώς αφαιρεί μία συγκεκριμένη επίθεση στις παρενέργειες κατά τη μεταγλώττιση.
try/catch#
Υπό use feature 'try' (σταθερό από την 5.40, διαθέσιμο στην 5.42), η κατασκευή try/catch/finally παρέχει αποκλειστική σύνταξη χειρισμού εξαιρέσεων:
use feature 'try';
try {
risky();
}
catch ($e) {
warn "caught: $e";
}
finally {
cleanup();
}
Διαφορές από το eval { ... }; if ($@) { ... }:
Η πιασμένη τιμή δένεται σε νέα λεκτική
$e(ή σε όποιο όνομα δοθεί), όχι στην$@. Δεν χρειάζεται ο χορός στιγμιότυπουmy $err = $@.Η
$@δεν τίθεται ως παρενέργεια - το μπλοκtryδεν έχει καθολική παρενέργεια σε αυτή.Η
tryδεν είναι από μόνη της βρόχος, αλλά οι τελεστές ροής ελέγχου συμπεριφέρονται ανάλογα με τηνeval BLOCK: ηreturnμέσα σεtryεπιστρέφει από την περικλείουσα υπορουτίνα, και οιnext/last/redoστοχεύουν εξωτερικό βρόχο.Η
finallyεκτελείται σε κάθε διαδρομή εξόδου και δεν μπορεί η ίδια να χρησιμοποιήσειreturn/next/last/redo. Είναι σημαδεμένη ως πειραματική στην 5.42 και εκπέμπει προειδοποίηση υπόexperimental::try.
Καταφύγετε στην try/catch σε νέο κώδικα. Το ιδίωμα eval-συν-$@ παραμένει σωστό και εξακολουθεί να είναι το σωστό εργαλείο όποτε ο περιβάλλων κώδικας πρέπει να τρέχει σε παλαιότερες Perls ή να μην απαιτεί την προστασία feature.
Παραδείγματα#
Παγίδευση σφάλματος χρόνου εκτέλεσης και συνέχιση:
my $q = eval { $x / $y };
if ($@) { warn "division failed: $@"; $q = 0 }
Ανίχνευση αν ένα χαρακτηριστικό είναι διαθέσιμο χωρίς τερματισμό κατά την εκκίνηση:
my $have_socket = eval { socket(my $s, PF_INET, SOCK_STREAM, 0); 1 };
die "no sockets on this build" unless $have_socket;
Φόρτωση αρθρώματος του οποίου η απουσία θα πρέπει να είναι ανεκτή:
my $json = eval { require JSON::XS; JSON::XS->new } //
do { require JSON::PP; JSON::PP->new };
Πιάσιμο blessed εξαίρεσης, επανεκτόξευση οποιουδήποτε άλλου:
eval {
parse($input);
};
if (my $err = $@) {
if (ref($err) && $err->isa('MyApp::ParseError')) {
report($err);
}
else {
die $err;
}
}
Εμφωλευμένη eval με local $@ ώστε ένα εσωτερικό προαιρετικό βήμα να μη σβήσει ένα εξωτερικό διαγνωστικό:
eval {
do_main_work();
{ local $@; eval { maybe_cleanup() } } # ignore cleanup errors
die "main still failed\n" if $something_else_wrong;
};
warn $@ if $@;
Καταστολή ενός καθολικά εγκαταστημένου γάντζου $SIG{__DIE__} για τη διάρκεια ιδιωτικής παγίδας:
eval {
local $SIG{__DIE__}; # don't trigger hooks while we probe
$answer = $x / $y;
};
warn $@ if $@;
Ένας γάντζος $SIG{__DIE__} που καταγράφει μόνο μη πιασμένες εξαιρέσεις, χρησιμοποιώντας τη $^S για να διακρίνει τις δύο περιπτώσεις:
$SIG{__DIE__} = sub {
return if $^S; # inside eval/try - let it propagate
log_fatal(@_); # top level - record before exit
};
Δυναμικά κατασκευασμένη διανομή (eval συμβολοσειράς που χρησιμοποιείται σκόπιμα, η είσοδος δεν προέρχεται από χρήστη):
for my $op (qw(add sub mul div)) {
eval "sub ${op}_of { \$_[0] "
. { add=>'+', sub=>'-', mul=>'*', div=>'/' }->{$op}
. " \$_[1] }";
die $@ if $@;
}
Οριακές περιπτώσεις#
Χωρίς όρισμα - η
eval;είναιeval $_. Σπάνια αυτό που θέλετε· γράψτε ρητάeval { ... }ήeval EXPR.Τελικό ερωτηματικό - μπορεί να παραλειφθεί από το τέλος του
EXPRή τουBLOCK. Ταeval "1+2"καιeval "1+2;"είναι ισοδύναμα.Η
eval BLOCKδεν είναι βρόχος - οιnext,lastκαιredoδεν μπορούν να αφήσουν ή να επανεκκινήσουν το μπλοκ. Ψάχνουν περικλείοντα βρόχο και κράζουν αν δεν υπάρχει.Η
returnμέσα σεeval BLOCKεπιστρέφει από την περικλείουσα υπορουτίνα, όχι από τηνeval- το μπλοκevalδεν είναι σώμα συνάρτησης. Για να παράγετε τιμή από το μπλοκ, βάλτε την τιμή ως την τελική έκφραση.Οι προειδοποιήσεις δεν παγιδεύονται. Η
warnμέσα σεevalεξακολουθεί να γράφει στοSTDERR. Δρομολογήστε τις στην$@μόνο αν θέσετε εσείς οι ίδιοι την$SIG{__WARN__}να το κάνει.Ροή ελέγχου έξω από την
eval- μιαgoto,lastήnextπου πηδά έξω από το μπλοκ παρακάμπτει την κανονική έξοδο τηςevalκαι συνεπώς ενδέχεται να αφήσει την$@ανέγγιχτη. Μην κατασκευάζετε λογική που εξαρτάται από αυτό.Εισαγωγικά στην
EXPR-eval $xέναντιeval "$x"συμπεριφέρονται πανομοιότυπα: και τα δύο εκτελούν τον κώδικα που περιέχει η$x. Τα διπλά εισαγωγικά προσθέτουν οπτικό θόρυβο, όχι σημασιολογία.eval '$x'έναντιeval { $x }- και τα δύο επιστρέφουν την τιμή της$x. Η μορφή μπλοκ μεταγλωττίζεται μαζί με το περιβάλλον πρόγραμμα· προτιμήστε την εκτός αν ο κώδικας χρειάζεται πράγματι να κατασκευαστεί σε χρόνο εκτέλεσης.eval ''μέσα στο πακέτοDB- δεν βλέπει τη συνηθισμένη περιβάλλουσα λεκτική εμβέλεια αλλά την εμβέλεια του πρώτου μη-DBκαλούντος. Αυτό το αντιμετωπίζουν μόνο όσοι γράφουν debugger.Αποτυχίες φόρτωσης XS - κάποια προβλήματα δυαδικής διεπαφής κατά την
requireενός αρθρώματος XS είναι μοιραία ακόμη και μέσα σεeval, εκτός αν τεθεί η$ENV{PERL_DL_NONLAZY}. Η συνηθισμένη περίπτωση είναι ασυμβατότητα εκδόσεων μεταξύ του τρέχοντος διερμηνέα και ενός προ-κατασκευασμένου.so. Το pperl δεν φορτώνει XS· η επιφύλαξη ισχύει όταν κώδικας μέσα σεevalαναθέτει τη φόρτωση σε ένα κλασικό perl5.
Διαφορές από το upstream#
Πλήρως συμβατό με το upstream Perl 5.42.
Δείτε επίσης#
die- το αντίστοιχο: εκτοξεύει την εξαίρεση που πιάνει ηeval. Ο κανόνας του τελικού newline στο μήνυμα διαμορφώνει το τι προσγειώνεται στην$@.do-do FILEδιαβάζει και αποτιμά ένα αρχείο όπως η eval συμβολοσειράς διαβάζει και αποτιμά μια συμβολοσειρά· χρήσιμο για αρχεία ρυθμίσεων γραμμένα σε Perl.return- μέσα σεeval BLOCKεπιστρέφει από την περικλείουσα υπορουτίνα, όχι από το μπλοκ· χρησιμοποιήστε μια τελική έκφραση για να επιστρέψετε τιμή από την ίδια τηνeval.require- συχνά τυλίγεται σεevalγια ανίχνευση προαιρετικών αρθρωμάτων χωρίς τερματισμό κατά τη μεταγλώττιση.evalbytes- eval συμβολοσειράς που μεταχειρίζεται το όρισμά της ως συμβολοσειρά bytes και επιτρέπει στα φίλτρα πηγαίου κώδικα να λειτουργούν κανονικά· χρησιμοποιήστε την όταν έχει σημασία η σημασιολογία επιπέδου byte.$@- εκεί όπου προσγειώνεται μια πιασμένη εξαίρεση, καθαρίζεται στην κενή συμβολοσειρά σε επιτυχημένηeval. Η σελίδα της οικογένειας error καλύπτει επίσης τον κλασικό κίνδυνο αλλοίωσης της$@.$^S- λέει στους χειριστές$SIG{__DIE__}και$SIG{__WARN__}αν είναι αυτή τη στιγμή ενεργή κάποιαevalήtry.$SIG{__DIE__}- γάντζος πριν την εκτόξευση· πυροδοτείται ακόμη και για εξαιρέσεις που μια περικλείουσαevalπρόκειται να πιάσει, που είναι ο συνηθισμένος λόγος για να τηνlocal-οποιήσετε για τη διάρκεια μιας παγίδας.