Κατάσταση σφάλματος#
Τέσσερις μεταβλητές αναφέρουν σφάλματα από τέσσερα διαφορετικά στρώματα. Δεν είναι εναλλάξιμες και συμπληρώνονται από εντελώς διαφορετικούς μηχανισμούς· η κακή χρήση μίας αντί άλλης είναι η πιο συνηθισμένη αιτία σφαλμάτων «γιατί δεν λειτουργεί ο χειρισμός σφαλμάτων μου» σε κώδικα Perl.
Μεταβλητή | Πηγή | Διάρκεια ζωής |
|---|---|---|
|
| Τίθεται σε αποτυχία κλήσης συστήματος· χωρίς νόημα διαφορετικά |
| Η εξαίρεση που συλλήφθηκε από την τελευταία | Τίθεται κατά την έξοδο |
| Κατάσταση εξόδου της τελευταίας διεργασίας-παιδιού | Τίθεται από κάθε |
| Πληροφορίες σφάλματος εκτεταμένες από το OS | Ίδια με τη |
Το καλύτερο πράγμα που μπορείτε να κάνετε για τον χειρισμό σφαλμάτων είναι να εσωτερικεύσετε ποια μεταβλητή απαντάει σε ποια ερώτηση:
«Απέτυχε η open/read/connect/κλήση συστήματος μου;» → ελέγξτε πρώτα την τιμή επιστροφής, μετά τη
$!.«Έπιασε το μπλοκ
evalμου μια εξαίρεση;» → ελέγξτε τη$@.«Ποιος ήταν ο κωδικός εξόδου του προγράμματος που μόλις έτρεξα;» → αποκωδικοποιήστε τη
$?.«Βρίσκομαι σε Windows ή VMS και χρειάζομαι κείμενο σφάλματος ειδικό του OS;» → δείτε επίσης τη
$^E.
$! - το σφάλμα συστήματος#
Η $! είναι η διεπαφή της Perl με το errno της βιβλιοθήκης C. Έχει διπλή φύση: διαβάστε τη σε αριθμητικό περιβάλλον για να πάρετε τον ακέραιο errno, σε περιβάλλον συμβολοσειράς για να πάρετε την αντίστοιχη συμβολοσειρά σφάλματος:
open(my $fh, '<', '/no/such/file') or do {
print "errno = ", $! + 0, "\n"; # numeric: 2
print "errstr = $!\n"; # string: "No such file or directory"
};
Κρίσιμο: η $! έχει νόημα μόνο αμέσως μετά από αποτυχία. Το errno της C δεν καθαρίζεται σε επιτυχία, οπότε μεταγενέστερη ανάγνωση μπορεί να δει παλιά τιμή από κάποια προηγούμενη κλήση συστήματος. Η αξιόπιστη μορφή:
if (open my $fh, '<', $path) {
# success - $! is meaningless here
process($fh);
} else {
# ONLY here is $! meaningful
die "open $path: $!";
}
Η εγγραφή αποτυχίας στην ίδια γραμμή με τη λειτουργία που την προκάλεσε είναι η ασφαλέστερη γραφή - δεν αφήνει ευκαιρία σε ενδιάμεση λειτουργία Perl να αλλοιώσει το errno.
%! - συμβολικοί έλεγχοι errno#
Η %! είναι ένα hash του οποίου τα κλειδιά είναι ονόματα συμβόλων errno όπως ENOENT, EACCES, EAGAIN. Ένα κλειδί ελέγχεται ως αληθές ακριβώς όταν η $! ισούται τη δεδομένη στιγμή με αυτό το errno:
unless (open my $fh, '<', $path) {
if ($!{ENOENT}) {
return []; # missing file → empty list
}
if ($!{EACCES}) {
die "permission denied: $path";
}
die "open $path: $!"; # anything else: re-raise
}
Αυτό είναι φορητό - οι αριθμητικές τιμές των συμβόλων errno διαφέρουν μεταξύ λειτουργικών συστημάτων, αλλά τα κλειδιά %! είναι πάντα τα ονόματα συμβόλων. Στο παρασκήνιο, η %! παρέχεται από το άρθρωμα Errno, το οποίο φορτώνεται κατ” απαίτηση την πρώτη φορά που χρησιμοποιείτε το hash.
Ανάθεση στη $!#
Η εγγραφή στη $! θέτει το errno στη βιβλιοθήκη C - χρήσιμο για κατασκευή μηνύματος σφάλματος που θα δει ο κώδικας παρακάτω:
$! = 13; # EACCES
print "$!\n"; # "Permission denied"
# Slightly more idiomatic - set errno by symbol:
use Errno qw(:POSIX);
$! = EACCES;
die "synthetic permission failure: $!";
$@ - η μεταβλητή εξαίρεσης#
Τίθεται από την eval όταν το μπλοκ (ή η συμβολοσειρά) που εκτέλεσε εκτόξευσε εξαίρεση. Περιέχει είτε την τιμή που μεταβιβάστηκε στη die (που συνήθως είναι συμβολοσειρά, αλλά μπορεί να είναι οποιαδήποτε αναφορά) είτε το μήνυμα σφάλματος ανάλυσης/χρόνου εκτέλεσης από τον διερμηνέα.
eval {
die "no port configured\n" unless defined $port;
open(my $sock, '<', "/dev/tcp/$host/$port") or die "connect: $!";
1;
};
if ($@) {
warn "could not open $host:$port - $@";
return;
}
Η επιτυχής αποτίμηση θέτει τη $@ σε κενή συμβολοσειρά ''. Ο έλεγχος if ($@) είναι επομένως αξιόπιστος: η κενή συμβολοσειρά είναι ψευδής, οποιαδήποτε μη κενή τιμή είναι αληθής.
Το κλασικό σφάλμα αλλοίωσης $@#
Υπάρχει κίνδυνος εδώ. Οτιδήποτε εκτελείται μεταξύ της εξόδου eval και του ελέγχου if ($@) μπορεί να αλλοιώσει τη $@. Ο πιο συνηθισμένος ένοχος είναι η μέθοδος DESTROY ενός αντικειμένου - όταν τα αντικείμενα βγαίνουν εκτός εμβέλειας στο κλείσιμο του μπλοκ eval, η DESTROY τους εκτελείται, και αν η DESTROY κάνει η ίδια κάποιο eval, η εξαίρεσή σας χάνεται:
eval {
my $obj = MyClass->new;
$obj->might_die;
1;
};
# If MyClass::DESTROY does an eval, $@ is now empty.
warn "got: $@"; # may print nothing!
Η ιστορικά συνιστώμενη λύση είναι να αποτυπώσετε τη $@ αμέσως, ή να χρησιμοποιήσετε το άρθρωμα Try::Tiny, που χειρίζεται αυτό και πολλές σχετικές παγίδες:
my $err;
{
local $@; # save and isolate
eval {
risky_op();
1;
} or $err = $@ || 'unknown error';
}
if ($err) {
warn "got: $err";
}
Το εγγενές χαρακτηριστικό try/catch της Perl 5.34+ είναι η καθαρότερη σύγχρονη γραφή - δεν χρησιμοποιεί καθόλου τη $@ και δεν επηρεάζεται από το πρόβλημα καταστροφέα:
use feature 'try';
try {
risky_op();
} catch ($err) {
warn "got: $err";
}
Η PetaPerl υποστηρίζει εγγενώς τα try/catch. Ο νέος κώδικας πρέπει να τα προτιμά.
eval και $!#
Η eval δεν διατηρεί τη $!. Αν το μπλοκ eval έκανε μια κλήση συστήματος που απέτυχε και μετά die, η $! είναι ό,τι έθεσε η τελευταία λειτουργία - που μέσα σε καταστροφέα ή χειριστή local $SIG{__DIE__} μπορεί να είναι οτιδήποτε. Αν το μήνυμα σφάλματός σας χρειάζεται τη $!, αποτυπώστε τη μέσα στην eval:
eval {
open(my $fh, '<', $path) or die "open $path: $!\n";
...
};
Η \n στο τέλος είναι σύμβαση - καταστέλλει το επίθεμα «at line N» που η Perl προσαρτά στα μηνύματα die, κάτι που είναι σωστό για σφάλματα προσανατολισμένα στον χρήστη.
$? - το σφάλμα παιδιού#
Μετά από system, waitpid, wait, εντολή με backticks, ή κλείσιμο σωλήνα, η $? περιέχει τη 16-bit κατάσταση wait() της διεργασίας-παιδιού. Δεν είναι απλώς ο κωδικός εξόδου:
system($cmd, @args);
my $status = $?;
my $exit_code = $status >> 8; # exit value passed to exit() in the child
my $signal = $status & 0x7F; # signal that killed the child, or 0
my $coredump = $status & 0x80; # set if the child dumped core
if ($status == -1) {
die "system: $!"; # could not even fork/exec
} elsif ($signal) {
die "$cmd died with signal $signal";
} elsif ($exit_code) {
die "$cmd failed (exit $exit_code)";
}
Η ολίσθηση >> 8 είναι η πιο αναφερόμενη παγίδα στην Perl. Μόνο το άνω byte είναι ο κωδικός εξόδου· το κάτω byte περιέχει τις σημαίες σήματος-και-core-dump. Αν ξεχάσετε την ολίσθηση παράγονται τιμές πολλαπλασιασμένες με 256.
Η σύγχρονη Perl παρέχει τις μακροεντολές WEXITSTATUS, WIFSIGNALED, WTERMSIG, WIFEXITED του αρθρώματος POSIX, που είναι οι φορητοί ονομαστικοί accessors:
use POSIX ':sys_wait_h';
if (WIFEXITED($?)) { my $code = WEXITSTATUS($?); ... }
if (WIFSIGNALED($?)) { my $sig = WTERMSIG($?); ... }
Η $? δεν διατηρείται μεταξύ άλλων λειτουργιών. Αν χρειάζεστε την τιμή, αποτυπώστε τη σε τοπική μεταβλητή στην επόμενη γραμμή - αλλιώς τα επόμενα backticks ή system θα την αντικαταστήσουν.
Η $? σε μπλοκ END#
Μέσα σε μπλοκ END, η $? περιέχει τον σκοπούμενο κωδικό εξόδου. Η ανάθεση σε αυτήν υπερισχύει της κατάστασης εξόδου του σεναρίου:
END {
$? = 0 if $? == 255 && $shutdown_was_clean;
}
$^E - το εκτεταμένο σφάλμα OS#
Σε Unix, η $^E είναι ακριβώς ίδια με τη $!. Σε Windows, περιέχει την τιμή GetLastError(), η οποία είναι μερικές φορές πιο ενημερωτική από το errno (στο οποίο τα Win32 αντιστοιχίζουν ένα αδρό υποσύνολο). Σε VMS, είναι ο εγγενής κωδικός κατάστασης VMS.
Η PetaPerl είναι μόνο για Linux. Η $^E και η $! είναι εναλλάξιμες εδώ· η μεταβλητή υπάρχει για φορητό κώδικα που μπορεί να τρέχει και σε μη-Unix perl.
Συνδυάζοντάς τα - ένα αναλυτικό παράδειγμα#
sub copy_file {
my ($src, $dst) = @_;
open my $in, '<', $src or die "open $src for reading: $!";
open my $out, '>', $dst or die "open $dst for writing: $!";
eval {
local $/ = \65536; # block reads, scoped to this eval
while (defined(my $chunk = <$in>)) {
print {$out} $chunk or die "write $dst: $!";
}
close $out or die "close $dst: $!";
1;
} or do {
my $err = $@ || 'unknown error';
unlink $dst; # clean up the partial file
close $in; # may set $! itself
die "copy_file($src → $dst): $err";
};
close $in or warn "close $src: $!";
return 1;
}
Τρεις μεταβλητές σφάλματος σε πέντε παραγράφους. Η $! αναφέρει κάθε αποτυχία κλήσης συστήματος στη γραμμή που την προκάλεσε. Η $@ συλλέγει κάθε εξαίρεση που πέρασε το όριο eval. Η $? δεν βρίσκεται σε αυτό το παράδειγμα γιατί δεν υπάρχει διεργασία-παιδί - αλλά αν η συνάρτηση εκτελούσε cp, θα αποκωδικοποιούσατε τη $? με την ίδια μορφή όπως στη σελίδα system.
Δείτε επίσης#
die- συμπληρώνει τη$@. Ο συνηθισμένος τρόπος για να εγείρετε εξαίρεση από τον δικό σας κώδικα.eval- πιάνει εξαιρέσεις· θέτει τη$@.try/catch- η σύγχρονη εναλλακτική στοeval/$@που παρακάμπτει τον κίνδυνο αλλοίωσης.Errno- η πηγή των κλειδιών%!· παρέχει ονομαστικές σταθερές errno.Try::Tiny- ανθεκτικό περιτύλιγμαevalπου διατηρεί σωστά τη$@.%SIG- οι$SIG{__DIE__}και$SIG{__WARN__}είναι τα hooks που ενεργοποιούνται όταν εκτελούνται ηdieκαι ηwarn· μπορούν να ξαναγράψουν τη$@.Λογικοί τελεστές - το ιδίωμα
or dieπου κινεί σχεδόν κάθε αναφορά$!.