Perl Part I · Module 8 · Conditional Statements
if / elsif / else
Conditionals in Perl look like conditionals everywhere else, with two differences that catch people on the first day and one that catches them later. The braces are not optional — a single-statement body still needs them, and leaving them off is a syntax error rather than a style warning. The keyword is spelled elsif, with no second e, and Perl is helpful enough to have a dedicated error message for the two spellings you are more likely to type. The one that catches people later is what Perl considers false, because the list is short, specific, and includes some strings that look like zero while excluding others that also look like zero.
Foundation14 min readPerlPart IControl FlowConditionals
Part I · Module 8 · Chapter 8.1 · if / elsif / else
1. The Shape
use strict;
use warnings;
for my $n (12, 7, 1, -4) {
if ($n > 10) { print "$n: over ten\n" }
elsif ($n > 5) { print "$n: over five\n" }
elsif ($n > 0) { print "$n: positive\n" }
else { print "$n: zero or less\n" }
}12: over ten
7: over five
1: positive
-4: zero or lessOnly the first true branch runs. 7 is greater than 5 and greater than 0, and it printed one line. Once a branch matches, Perl skips the rest of the chain — which is why the order of the tests is part of the logic, not a detail. Reverse the first two branches here and 12 would report over five.
The comparison operators themselves come from Chapter 3.5 and Chapter 3.6: == != < > for numbers, eq ne lt gt for text. Getting that choice wrong is the single most common conditional bug in Perl and Chapter 3.5 is the chapter on it.
2. The Braces Are Mandatory
use strict;
use warnings;
my $n = 5;
if ($n > 3) print "big\n";syntax error at nobraces.pl line 6, near ") print"
Execution of nobraces.pl aborted due to compilation errors.255C, C++, Java and SystemVerilog all allow a single statement with no braces. Perl does not. The body of an if is a block, and a block needs braces — there is no one-statement shortcut.
This is good news in practice, because the bodyless form is the cause of a well-known family of bugs in those other languages: add a second line to the body, forget to add braces, and the second line runs unconditionally. Perl makes that impossible.
If what you actually want is a one-line conditional, Perl has a different form for it — the statement modifier, which puts the condition at the end:
print "big\n" if $n > 3;That is Chapter 8.3's subject, and it is the idiomatic way to write a short guard.
3. It Is Spelled elsif
Perl has a dedicated error message for this, which tells you how often it is mistyped.
use strict;
use warnings;
my $n = 5;
if ($n > 9) { print "huge\n" }
elseif ($n > 3) { print "mid\n" }elseif should be elsif at elseif.pl line 7.
syntax error at elseif.pl line 7, near ") {"
Execution of elseif.pl aborted due to compilation errors.The two-word form fails differently:
use strict;
use warnings;
my $n = 5;
if ($n > 9) { print "huge\n" }
else if ($n > 3) { print "mid\n" }syntax error at elseif2.pl line 7, near "else if"
Execution of elseif2.pl aborted due to compilation errors.elseif should be elsif names the fix outright. syntax error ... near "else if" does not, so the two-word habit from C costs a few more seconds. Both are compile errors, so neither can ship.
4. What Perl Considers False
This is the part that matters later, and the list is worth memorising because it is short.
use strict;
use warnings;
my @cases = (
[ q{0}, 0 ],
[ q{1}, 1 ],
[ q{-1}, -1 ],
[ q{""}, "" ],
[ q{"0"}, "0" ],
[ q{"00"}, "00" ],
[ q{"0.0"}, "0.0" ],
[ q{"0E0"}, "0E0" ],
[ q{" "}, " " ],
[ q{"false"}, "false" ],
[ q{0.0}, 0.0 ],
);
for my $c (@cases) {
printf "%-9s is %s\n", $c->[0], ($c->[1] ? "TRUE" : "false");
}
my $u;
{ no warnings 'uninitialized'; printf "%-9s is %s\n", "undef", ($u ? "TRUE" : "false"); }
printf "%-9s is %s\n", "[]", ([] ? "TRUE" : "false");
printf "%-9s is %s\n", "empty \@a", (do { my @a; @a ? "TRUE" : "false" });0 is false
1 is TRUE
-1 is TRUE
"" is false
"0" is false
"00" is TRUE
"0.0" is TRUE
"0E0" is TRUE
" " is TRUE
"false" is TRUE
0.0 is false
undef is false
[] is TRUE
empty @a is falseSix things are false, and that is the whole list:
| False | Note |
|---|---|
0 | the number zero |
0.0 | because it is the number zero |
"" | the empty string |
"0" | the one-character string zero |
undef | never assigned, or explicitly undefined |
| an empty array or hash | in boolean context |
Everything else is true. In particular:
"00"is TRUE. Two characters, and not the exact string"0"."0.0"is TRUE — a three-character string. Note0.0the number is false. The same-looking value behaves differently depending on whether it arrived as a number or as text, which is exactly what happens when a field is read from a report file." "is TRUE. A single space is a non-empty string."false"is TRUE, which surprises people coming from languages that have a real boolean type.[]is TRUE — an empty array reference, which Chapter 7.1 §6 covered.if ($aref)andif (@$aref)ask different questions.
5. A Chain With No else
use strict;
use warnings;
my $slack = 0.34;
if ($slack < -0.1) { print "critical\n" }
elsif ($slack < 0) { print "marginal\n" }
print "done\n";doneThe else is optional, and when every condition is false, nothing happens and nothing is reported. That is correct behaviour and occasionally the bug.
A chain whose job is to classify something should usually end in an else — but be careful about what you expect that else to catch, because it is less than you would think.
use strict;
use warnings;
sub classify {
my ($slack) = @_;
no warnings 'uninitialized', 'numeric';
return "critical" if $slack < -0.1;
return "marginal" if $slack < 0;
return "ok" if $slack >= 0;
return "UNCOMPARABLE";
}
my $nan = "nan" + 0;
printf "%-9s -> %s\n", "0.34", classify(0.34);
printf "%-9s -> %s\n", "undef", classify(undef);
printf "%-9s -> %s\n", "N/A", classify("N/A");
printf "%-9s -> %s\n", "NaN", classify($nan);0.34 -> ok
undef -> ok
N/A -> ok
NaN -> UNCOMPARABLEundef and "N/A" both came back ok. Only NaN reached the final branch.
Here is why, measured directly:
undef : < -0.1 = false < 0 = false >= 0 = true
N/A : < -0.1 = false < 0 = false >= 0 = true
NaN : < -0.1 = false < 0 = false >= 0 = falseundef and "N/A" numify to 0, and 0 >= 0 is true — so they are classified as a passing slack. Across nine test values, exactly one made all three comparisons false, and that was NaN, whose incomparability Chapter 3.7 measured.
So the lesson is the opposite of the comfortable one. A final else on a chain of numeric comparisons is not a safety net for missing or malformed data — it catches a case you will almost never see, and lets through the two you will see constantly. Missing data does not announce itself by failing every comparison; it announces itself by quietly passing one.
A three-way classification cannot be built out of comparisons alone. It needs a different kind of test first:
return "UNKNOWN" unless defined $slack;
return "UNKNOWN" unless $slack =~ /^[-+]?\d+(?:\.\d+)?$/;
return "critical" if $slack < -0.1;
return "marginal" if $slack < 0;
return "ok";defined and a pattern match answer "do I have a usable number", and only then do the comparisons get to answer "which band". Chapter 8.6 is the whole chapter on that ordering, and it measures what a check without it reports.
6. Why This Matters in Engineering Work
Branch order is logic. An elsif chain that classifies by threshold must run from the narrowest condition outward. Written the other way round it still compiles, still runs, and reports every critical violation as merely marginal.
The false list decides what your guards do. if ($count) skips a block with zero items, which is usually what you want. if ($slack) skips a path with exactly zero slack, which is almost never what you want — and a slack of zero is a perfectly ordinary timing result.
And text from a file is text. The single most common source of a conditional that behaves differently in production than in your test is a value that was a number when you typed it and a string when the tool produced it. defined, a pattern match, or a numeric comparison all avoid it; a bare truth test does not.
7. Common Mistakes
Omitting the braces. Perl has no bodyless if. Use a statement modifier for a one-liner.
elseif or else if. The keyword is elsif. Perl names the fix for the first form only.
Ordering an elsif chain from widest to narrowest. The first match wins, so the narrow cases never run.
if ($value) to test presence. Use defined. 0, "0" and "" are all present and all false.
Expecting "0.0" to be false. The string is true; the number is false.
Expecting "00" or " " to be false. Both are true.
Reaching for given/when. It is experimental and warns on every run. Use an elsif chain or a dispatch table.
A classifying chain with no else. When nothing matches, nothing happens and nothing is reported.
8. Exercise
Exercise 1 — the chain. Write a four-branch chain that classifies 12, 7, 1 and -4 as over ten, over five, positive, or zero-or-less. Confirm each input produces exactly one line.
Exercise 2 — order matters. Swap the first two branches and rerun. Record what 12 now reports and explain it in one sentence using the phrase "first match".
Exercise 3 — no braces. Write if ($n > 3) print "big\n"; and record the exact error and exit status. Then rewrite it as a one-liner using a statement modifier.
Exercise 4 — both typos. Write elseif and then else if. Record both diagnostics and note which one tells you the fix.
Exercise 5 — the truth table. Test all fourteen values from §4. Write down the six that are false. Then explain in one sentence why 0.0 is false but "0.0" is true.
Exercise 6 — the file-versus-arithmetic trap. Set one variable to the string "0.0" and another to the number 0.0. Print whether each is true. Then write the three questions from §4's callout — defined, a numeric comparison, and a pattern match — and say which you would put in a report script.
Exercise 7 — given/when. Run §3's given/when example. Record STDOUT and STDERR. Write one sentence on why you would not put it in a shared script.
Exercise 8 — what the final else actually catches. Write §5's three-branch slack classifier with a final branch returning UNCOMPARABLE. Call it with 0.34, undef, the string "N/A", and "nan" + 0. Record which of the four reached the last branch. Then, for the three non-numeric inputs, print each of the three comparisons separately and explain in one sentence why two of them were classified ok. Finally rewrite the classifier with a defined test and a numeric pattern match in front, and confirm all three now report UNKNOWN.
9. Summary
if/elsif/else, and only the first true branch runs. Branch order is part of the logic.- The braces are mandatory, even for one statement. Omitting them is a syntax error, exit 255.
- The keyword is
elsif.elseifgets a dedicatedelseif should be elsifdiagnostic;else ifgets a plain syntax error. - Exactly six things are false:
0,0.0,"","0",undef, and an empty array or hash. "00","0.0","0E0"," ","false"and[]are all true.0.0is false but"0.0"is true — the same value from a file behaves differently from the same value from arithmetic.- Test presence with
defined, not with truth. given/whenis experimental and warns every run. Use anelsifchain, or Chapter 7.11's dispatch table when the cases are many.- A classifying chain should end in an
else, becauseundefand non-numeric text make every comparison false.
Chapter 8.2 covers the same decision written the other way round: unless, which runs a block when a condition is false, and the point past which it stops being easier to read.
Where this fits
Part of the Perl curriculum.
