I’ve noticed that Exception.pm and Error.pm don’t seem to be extensively used in the Perl community. Is that due to the large footprint of eval
for exception handling?
Also Perl programs appear to have a much more lenient policy regarding exception handling in general. Is there a compelling reason for this?
In any event what would be the best method for exception handling in Perl?
the Tin Man
158k42 gold badges214 silver badges303 bronze badges
asked Oct 23, 2010 at 21:53
ennuikillerennuikiller
46.3k14 gold badges112 silver badges137 bronze badges
2
The consensus of the Perl community seems to be that Try::Tiny is the preferred way of doing exception handling. The «lenient policy» you refer to is probably due to a combination of:
- Perl not being a fully object-oriented language. (e.g. in contrast to Java where
you can’t avoid dealing with exceptions.) - The background of many Perl developers. (Languages like C1 and shell don’t have
exception mechanisms.) - The kind of tasks people tend to use Perl for. (Small scripts for text munging and
report generation where exception handling isn’t needed.) - Perl not having a (good) built-in exception mechanism.
Note that the last item means that you’ll see a lot of code like this:
eval { something() };
if ($@) {
warn "Oh no! [$@]n";
}
That’s exception handling even though it doesn’t use try/catch syntax. It’s fragile, though, and will break in a number of subtle edge cases that most people don’t think about. Try::Tiny and the other exception handling modules on CPAN were written to make it easier to get right.
1. C does have setjmp()
and longjmp()
, which can be used for a very crude form of exception handling.
answered Oct 23, 2010 at 22:08
Michael CarmanMichael Carman
30.6k9 gold badges74 silver badges122 bronze badges
3
Never test $@ as is, because it is a global variable, so even the test itself can change it.
General eval-template:
my $result;
eval {
$result= something();
# ...
1; # ok
} or do {
my $eval_error= $@ || "error";
# ...
die $eval_error;
}; # needs a semicolon
In practice that is the lightest way. It still leaves a tiny room for funny $@ behaviour, but nothing that really concerned me enough.
answered Jan 17, 2019 at 9:45
As it has been mentioned you can use the traditional way with eval, but if you want to use more elaborate exception trapping, including with exception objects, then I recommend using the try-catch-finally blocks.
There are quite a few perl modules that provide it such as Nice::Try and Syntax::Keyword::Try, but Syntax::Keyword::Try does not provide exception variable assignment or exception class catch like
try
{
# something
}
catch( Exception $e )
{
# catch this in $e
}
Full disclosure: I am the developer of Nice::Try
answered Jun 2, 2020 at 4:45
JacquesJacques
9711 gold badge12 silver badges15 bronze badges
1
Perl eval built-in function is very powerful. In this article let us review how to use regex in eval, trapping errors using eval, create dynamic code using eval, insert a code from a file/sub-routine using eval etc.,
The general form of perl eval expects a expression or a block of code as an argument.
1. Differences Between Various Perl Eval Statements
Let us consider the following perl code snippet:
$expr = ‘$a + $b’;
eval $expr; # 1
eval “$expr”; # 2
eval ‘$expr’; # 3
eval { $expr }; # 4
Case #1 and #2 are identical where perl identifies them as a valid executable statement and evaluates the expression. So, it would produce the result as “30”.
In case #3, the expression cannot be expanded. So, it gives actual expression ($a + $b) as a result. This wont be commonly used by anyone.
Case #4 is identical to case #3, but the statements inside the block is validated for syntax errors at compile time.
2. Regular Expressions Handling with Eval
With the use of eval, we can reduce the number of lines in the code considerably when it needs to match the line for more than a number of regular expressions.
The list of regular expressions can be put into a container (hash / array), and in a loop the regular expressions can be taken one by one and matches with the input line as shown below.
$line = <>; %hash = ( number => qr/^[0-9]+$/, alphabets => qr/^[a-zA-Z]+$/ ); while( my ($key,$value) = each(%hash) ) { if(eval "$line =~ /$value/") { print "$keyn"; } }
3. Perl Eval Error Handling – Trapping Errors
Eval is used to trap the errors. During the execution of the subroutine the program might die because of errors, or external calling of die function. During this time, if the block of perl code is executed inside the eval, then program continues to run even after the die or errors, and it also captures the errors or dieing words.
Zero Divide Error:
eval { $average = $total / $count }; print “Error captured : $@n”;
In the above, $count contains the value as 0. When we run the code without the eval block, the program gets exit.
Run After Die
sub func { die “dieing in subroutine funcn”; } eval { func(); }; print “Error captured : $@n”;
Perl eval can’t catch following errors:
- Uncaught signal
- Running out of memory
- Syntax errors
4. Dynamic Perl Code Using Eval
Eval can compile and executes code from a string at runtime. So, we can write the dynamic perl program using eval.
print “Enter number 1 : ”; $data1 = ; print “Enter number 2 : ”; $data2 = ; print “Enter operator : ”; $operator = ; $str = “$data1 $operator $data2”; $result = eval “$data1 $operator $data2”;
We can execute any number of statements in eval. The result of the eval is the last evaluated expression.
We should be care in using eval on a string, as it might execute untrusted data from a string. If the assignment operations has to be done in the string itself, we should be care on handling the lvalue.
$str = “$result = $data1 $operator $data2”; eval { $str };
5. Inserting Perl Script From a File or Subroutine during Run Time
In perl we can dynamically load modules at run time. To load modules dynamically, we have to use “require” ( not “use”).
Sample code to load module when the input file is a compressed one,
$filetype = `file $InputFile`; if($filetype =~ /gzip compressed/) { print "compressed input file...n"; eval { require PerlIO::gzip; }; }
Common subroutines that are used in the programs can be separated into a file that can be loaded into the perl programs by eval as shown below.
File : common_routines.pl sub open_file { .... } sub read_file { .... } sub write_file { .... } sub close_file { .... } In the perl script, sub load_file_subroutines { open CODE, 'common_routines.pl'; undef $; my $code = <code>; close CODE; eval $code; die $@ if $@; }</code>
6. Perl Eval in Input Time-Outs
The alarm standard library function is used to generate SIGALRM signal after a certain time. This would mainly helps us to avoid the blocking wait on the user input. This can be used with eval to get the user input within a specified time as shown in the example below.
$SIG{ALRM} = &input_timed_out; eval { alarm (10); $buf = <>; alarm(0); # Cancel the pending alarm if user responds. }; if ($@ =~ /NO USER INPUT IN TIME/) { print "Timed out.n"; } sub input_timed_out { die "NO USER INPUT IN TIME"; }
Chapter 3. Essential skills
The standard Perl syntax for handling exceptions (die/eval) is quirky and has
some pitfalls that are easy to tumble into. However its pretty common so
you will need to understand it.
Throwing exceptions
To throw an exception call die()
. If the exception is not caught, an error
message is displayed to STDERR and the process exits with a non-zero value.
die "Something bad happened";
# prints "Something bad happened at line 123."
# and then the process exits;
Catching exceptions
To catch an exception, use eval()
. eval()
parses, compiles, and evaluates
a block of code at compile time and catches any exceptions that are raised at
runtime. The exception is placed in the global variable $@
.
eval { die "Something bad happened" }; # try (and catch)
warn $@ if $@; # handle exception
Exceptions as objects
Exceptions are usually strings, but you can throw objects too.
eval {
die My::Exception->new(
error => 'Something bad happened',
request => $request,
response => $response,
);
};
warn $@->error if $@;
Pitfalls
1. $@
is a global variable
If your exception handling code calls eval()
, $@
will get clobbered. This
is easy to forget. Here is one way to avoid it:
eval { die "something bad" };
if ($@) {
my $error = $@;
disconnect_from_the_database(); # calls eval()
warn $error;
}
If you are a module author and want to be polite and not modify $@
globally
you need to jump through hoops like this:
my ($error1, $error2);
{
local $@;
unless (eval { ...; return 1 }) {
$error1 = 1;
$error2 = $@;
}
}
if ($error1) {
# handle exception
}
2. Exception objects that evaluate as false
You can overload Perl operators. For
example, you could have an exception object evaluate to «error» in string
context. You could also have an exception object evaluate to -1 in string
context.
This would cause mysterious problems for most people because the common
idiom handle_exception() if $@
will silently fail and the exception won’t be
handled.
One solution is to use a safer but more verbose idiom everywhere:
unless ( eval { try_something_risky(); return 1 } ) {
handle_exception();
}
3. eval
blocks behave more like anonymous subroutines than if blocks.
For example:
- A
return
statement will exit theeval
block — not the containing function. - Loop control statements like
redo
,next
, andlast
only work in the context of whats inside theeval
block.
See also
For more info read the documentation for
die() and
eval()
Иногда даже самый обычный, повседневный код приводит к фатальным ошибкам в программе. Любая из следующих типичных команд может стать причиной аварийного завершения:
$barney = $fred / $dino; # Деление на нуль? print "matchn" if /^($wilma)/; # Некорректное регулярное выражение? open CAVEMAN, $fred # Ошибка пользователя приводит к сбою die? or die "Can't open file '$fred' for input: $!";
Некоторые из этих ошибок можно выявить заранее, но обнаружить их все невозможно. (Как проверить строку $wilma из этого примера и убедиться в том, что она содержит корректное регулярное выражение?) К счастью, в Perl имеется простой способ перехвата фатальных ошибок – проверяемый код заключается в блок eval:
eval { $barney = $fred / $dino } ;
Даже если переменная $dino равна нулю, эта строка не приведет к сбою программы. В действительности eval является выражением (а не управляющей конструкцией, как while или foreach), поэтому точка с запятой в конце блока обязательна. Если во время выполнения блока eval происходит фатальная (в обычных условиях) ошибка, блок продолжает работать, но программа аварийно не завершается. Таким образом, сразу же после завершения eval желательно проверить, завершился ли блок нормально или произошла фатальная ошибка. Ответ содержится в специальной переменной $@. Если в блоке была перехвачена фатальная ошибка, $@ будет содержать «последние слова» программы – сообщение вида «Недопустимое деление на нуль в my_program строка 12». Если ошибки не было, переменная $@ пуста. Конечно, это означает, что $@ содержит удобный логический признак для проверки (true в случае ошибки), поэтому после блоков eval часто встречается команда следующего вида:
print "An error occurred: $@" if $@;
Блок eval является полноценным блоком, поэтому он определяет новую область видимости для лексических (my) переменных. В следующем фрагменте показан блок eval в действии:
foreach my $person (qw/ fred wilma betty barney dino pebbles /) { eval { open FILE, "<$person" or die "Can't open file '$person': $!"; my($total, $count); while (<FILE>) { $total += $_; $count++; } my $average = $total/$count; print "Average for file $person was $averagen"; &do_something($person, $average); }; if ($@) { print "An error occurred ($@), continuingn"; } }
Сколько возможных фатальных ошибок перехватывает этот блок? Если произойдет ошибка при открытии файла, она будет перехвачена. Вычисление среднего арифметического может привести к делению на нуль, и эта ошибка тоже перехватывается. Даже вызов загадочной функции &do_something защищается от фатальных ошибок, потому что блок eval перехватывает все фатальные ошибки, происходящие во время его активности. (В частности, это будет удобно, если вы вызываете пользовательскую функцию, написанную другим программистом, но не знаете, насколько надежно она написана.) Если ошибка происходит в ходе обработки одного из файлов, мы получим сообщение об ошибке, но программа спокойно перейдет к следующему файлу. Блоки eval даже могут вкладываться в другие блоки eval. Внутренний блок перехватывает ошибки во время выполнения, не позволяя им добраться до внешних блоков. (Конечно, после завершения внутреннего блока eval можно «перезапустить» ошибку при помощи die, чтобы она была перехвачена внешним блоком.) Блок eval перехватывает любые ошибки, происходящие во время выполнения, в том числе и ошибки при вызове функций (как показано в предыдущем примере).
Ранее мы уже упоминали о том, что eval является выражением, и поэтому после завершающей фигурной скобки необходима точка с запятой. Но как и любое выражение, eval имеет некоторое возвращаемое значение. При отсутствии ошибок значение вычисляется по аналогии с функциями: как результат последнего вычисленного выражения или как значение, возвращаемое на более ранней стадии необязательным ключевым словом return. А вот другой способ выполнения вычислений, при котором вам не нужно беспокоиться о делении на нуль:
my $barney = eval { $fred / $dino };
Если eval перехватывает фатальную ошибку, возвращаемое значение представляет собой либо undef, либо пустой список (в зависимости от контекста). Таким образом, в предыдущем примере $barney либо содержит правильный результат деления, либо undef; проверять $@ необязательно (хотя, вероятно, перед дальнейшим использованием $barney стоит включить проверку defined($barney)). Существуют четыре вида проблем, которые eval перехватить не может. Первую группу составляют очень серьезные ошибки, нарушающие работу Perl, – нехватка памяти или получение необработанного сигнала. Поскольку Perl при этом перестает работать, перехватить эти ошибки он не сможет. Конечно, синтаксические ошибки в блоке eval перехватываются на стадии компиляции – они никогда не возвращаются в $@. Оператор exit завершает программу немедленно, даже если он вызывается в пользовательской функции в блоке eval. (Отсюда следует, что при написании пользовательской функции в случае возникновения проблем следует использовать die вместо exit.)
Четвертую и последнюю разновидность проблем, не перехватываемых блоком eval, составляют предупреждения – пользовательские (из warn) или внутренние (включаемые ключом командной строки –w или директивой use warnings). Для перехвата предупреждений существует специальный механизм, не связанный с eval; за информацией обращайтесь к описанию псевдосигнала __WARN__ в документации Perl. Стоит также упомянуть еще об одной форме eval, которая может быть опасна при неправильном использовании. Более того, некоторые люди считают, что eval вообще не следует использовать в программах по соображениям безопасности. Действительно, eval следует использовать с осторожностью, но здесь имеется в виду другая форма eval – «eval для строк». Если же за ключевым словом eval сразу же следует блок программного кода в фигурных скобках, беспокоиться не о чем – эта разновидность eval безопасна.
Originally the accepted way in Perl to signal an error was to return
undef and let the users of the function or module
decide what they want to do with it. If they even look at it.
In other languages throwing an exception is the standard way to signal an error.
Some of the Perl modules on CPAN follow the «return undef» path, others will throw an exception.
The question how to deal with that?
Parse dates
In this example we use the Time::Piece module
to parse strings represent dates. The strptime method accept a string representing
a date and a format string using strptime formatting instructions. It then tries to parse the
string according to the formatting instructions. In our example the formatting instructions
«%d %b, %Y» mean we are expecting to have a number representing the days, followed
by a string representing the month (e.g. ‘Nov’), and then a 4-digit number representing the year.
examples/time_piece.pl
use 5.010; use strict; use warnings; use Time::Piece; my @dates = ("3 Nov, 1989", "Nov, 1989", "1 Jan, 1999"); foreach my $d (@dates) { my $tp = Time::Piece->strptime($d, "%d %b, %Y"); say $tp; }
If we run this code we’ll see the following output:
$ perl examples/time_piece.pl Fri Nov 3 00:00:00 1989 Error parsing time at .../Time/Piece.pm line 469.
What happened here is that we tried to parse 3 strings, but the 2nd string was incorrectly formatted.
The strptime method raised an exception to indicate this failure (probably by calling die)
that caused our script to die.
Catch the exception using eval
While there are other ways to handle exceptions using, for example
Try::Tiny, in this case we look
at the built-in eval statement.
If we wrap a piece of code in an eval block, the eval will capture any exception that might
happen within that block, and it will assign the error message to the $@ variable of Perl.
The simple way to do this looks like this:
(please note, there is a ; after the eval block.)
eval { # code that might throw exception }; if ($@) { # report the exception and do something about it }
A more robust way to write this looks like this:
eval { # code that might throw exception 1; # always return true to indicate success } or do { # this block executes if eval did not return true (because of an exception) # save the exception and use 'Unknown failure' if the content of $@ was already # removed my $error = $@ || 'Unknown failure'; # report the exception and do something about it };
Here too, there is a trailing ;, but it is only after the do block.
Error handling for Time::Piece
We used the eval or do expression we have just seen.
In this solution we have both the creation of $tp
and its use inside the eval block. That’s because if we cannot parse
the date-string then there is no point in trying to us that variable.
examples/time_piece_eval.pl
use 5.010; use strict; use warnings; use Time::Piece; my @dates = ("3 Nov, 1989", "Nov, 1989", "1 Jan, 1999"); foreach my $d (@dates) { eval { my $tp = Time::Piece->strptime($d, "%d %b, %Y"); say $tp; 1; } or do { my $error = $@ || 'Unknown failure'; warn "Could not parse '$d' - $error"; }; }
If we run this script we’ll get the following:
Fri Nov 3 00:00:00 1989 Could not parse 'Nov, 1989' - Error parsing time at .../Time/Piece.pm line 469. Fri Jan 1 00:00:00 1999
This means, that although the second string could not parse and Time::Piece raised
an exception, we captured that exception, reported it and then let the foreach
loop go on to the third value that was properly parsed and printed.
In the comments, please wrap your code snippets within <pre> </pre> tags and use spaces for indentation.