Skip to content
VLSI Mentor

Perl Part I · Module 4 · Strings and Text

Changing Case and Trimming Whitespace

Chapter 4.5 ended on a problem: every field pulled out of a fixed-width report arrived with padding attached, so a status column that prints as the word PASS is not equal to the string PASS. This chapter fixes that, and fixes the matching problem of case — a tool that writes PASS on Monday and Pass after an upgrade has given you two different values for one outcome, and a report built on them will show two rows where there should be one. The functions involved are small. What is worth the attention is the discipline of normalising at the point you read a value in, and one measurement in this chapter where two fields print identically, compare differently, and only their lengths tell them apart.

Foundation13 min readPerlPart IFundamentalsStringsData Cleaning

Part I · Module 4 · Chapter 4.6 · Changing Case and Trimming Whitespace

1. Four Functions, and They Return Rather Than Modify

Azvya Education Pvt. Ltd.VLSI Mentor
case-functions.pl
use strict;
use warnings;

my $s = "alu_Top";

printf "original : %s\n", $s;
printf "uc       : %s\n", uc($s);
printf "lc       : %s\n", lc($s);
printf "ucfirst  : %s\n", ucfirst($s);
printf "lcfirst  : %s\n", lcfirst($s);
printf "unchanged: %s\n", $s;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
original : alu_Top
uc       : ALU_TOP
lc       : alu_top
ucfirst  : Alu_Top
lcfirst  : alu_Top
unchanged: alu_Top

Note the last line. These functions return a new value and leave their argument alone — the rule from Chapter 3.1 §5, in contrast to the substr forms from Chapter 4.5 §4 which deliberately do modify. If you want the variable changed, assign the result back:

Azvya Education Pvt. Ltd.VLSI Mentor
Assigning the result back
$s = uc $s;

And note what ucfirst did: alu_Top became Alu_Top. Only the first character changed. The capital T in the middle was already uppercase and was left exactly as it was.

2. ucfirst Is Not Title Case

This is the misconception worth killing immediately.

Azvya Education Pvt. Ltd.VLSI Mentor
ucfirst-misconception.pl
use strict;
use warnings;

my $s = "ALU_TOP";

printf "ucfirst(\$s)    : %s\n", ucfirst($s);
printf "ucfirst(lc \$s) : %s\n", ucfirst(lc $s);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
ucfirst($s)    : ALU_TOP
ucfirst(lc $s) : Alu_top

ucfirst("ALU_TOP") is ALU_TOP — nothing happened. The first character was already uppercase, and ucfirst only ever touches the first character.

ucfirst means "uppercase character zero". It does not mean "make this look like a title". For that you have to lowercase everything first and then raise the first character, which is the ucfirst(lc $s) idiom in the second line.

Reading that expression requires the rule from Chapter 3.1: the inner call is reduced to a value before the outer one receives it. lc $s becomes alu_top, and ucfirst then raises the a.

3. Trimming Whitespace

Perl has no built-in trim. The idiom is a pair of substitutions:

Azvya Education Pvt. Ltd.VLSI Mentor
trim.pl
use strict;
use warnings;

my $field = "   DONE   ";

my $trimmed = $field;
$trimmed =~ s/^\s+//;
$trimmed =~ s/\s+$//;

printf "raw     : [%s] len=%d\n", $field,   length($field);
printf "trimmed : [%s] len=%d\n", $trimmed, length($trimmed);
printf "raw     eq 'DONE' : %s\n", ($field   eq "DONE" ? "true" : "false");
printf "trimmed eq 'DONE' : %s\n", ($trimmed eq "DONE" ? "true" : "false");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
raw     : [   DONE   ] len=10
trimmed : [DONE] len=4
raw     eq 'DONE' : false
trimmed eq 'DONE' : true

Ten characters became four, and the comparison went from false to true. That is the whole value of the operation, and it is the direct answer to Chapter 4.5 §6.

The two substitutions can be combined onto one line:

Azvya Education Pvt. Ltd.VLSI Mentor
trim-oneline.pl
use strict;
use warnings;

my $field = "   DONE   ";

my $trimmed = $field;
$trimmed =~ s/^\s+|\s+$//g;

printf "trimmed : [%s] len=%d\n", $trimmed, length($trimmed);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
trimmed : [DONE] len=4

Trimming does not touch the middle

Azvya Education Pvt. Ltd.VLSI Mentor
interior.pl
use strict;
use warnings;

my $s = "  a  b  ";

my $t = $s;
$t =~ s/^\s+|\s+$//g;

printf "[%s] -> [%s]   len %d -> %d\n", $s, $t, length($s), length($t);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
[  a  b  ] -> [a  b]   len 8 -> 4

The two spaces between a and b survived, because the pattern only anchors to the start and the end. That is almost always what you want — a description field containing real spaces should keep them. Collapsing interior runs of whitespace is a separate operation and a separate decision.

4. What \s Actually Covers

Worth measuring rather than assuming, because "whitespace" is vaguer than it sounds:

Azvya Education Pvt. Ltd.VLSI Mentor
whitespace-set.pl
use strict;
use warnings;

for my $pair ([" ", "space"], ["\t", "tab"], ["\n", "newline"],
              ["\r", "carriage return"], ["\f", "form feed"], ["\0", "NUL"]) {
    my $wrapped = "X" . $pair->[0] . "X";
    my $stripped = $wrapped;
    $stripped =~ s/\s//g;

    printf "%-16s ord=%-3d removed by \\s: %s\n",
        $pair->[1], ord($pair->[0]), (length($stripped) == 2 ? "yes" : "no");
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
space            ord=32  removed by \s: yes
tab              ord=9   removed by \s: yes
newline          ord=10  removed by \s: yes
carriage return  ord=13  removed by \s: yes
form feed        ord=12  removed by \s: yes
NUL              ord=0   removed by \s: no

The five usual suspects are covered, including the carriage return — which matters, because \s is therefore enough to clean up a value read from a Windows-written file. The code points are the ones measured in Chapter 4.3 §2.

NUL is not whitespace. \s is not "any non-printing character", so a field containing a stray NUL is not cleaned by trimming. That is rare and worth knowing about rather than planning for.

The case this is really for

Azvya Education Pvt. Ltd.VLSI Mentor
windows-value.pl
use strict;
use warnings;

my $from_windows = "PASS\r";

my $t = $from_windows;
$t =~ s/^\s+|\s+$//g;

printf "raw     len=%d  eq 'PASS': %s\n", length($from_windows), ($from_windows eq "PASS" ? "yes" : "no");
printf "trimmed len=%d  eq 'PASS': %s\n", length($t),            ($t            eq "PASS" ? "yes" : "no");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
raw     len=5  eq 'PASS': no
trimmed len=4  eq 'PASS': yes

Five characters where you expected four, an invisible difference, and a comparison that fails for no apparent reason. Chapter 4.3 §2 introduced the carriage return; Chapter 4.9 is where the line-ending question is handled properly.

5. Normalising, and a Table That Needs Its Length Column

Put the two operations together and you get normalisation: one canonical form for values that mean the same thing.

Azvya Education Pvt. Ltd.VLSI Mentor
normalise.pl
use strict;
use warnings;

my @raw = ("PASS", "pass", " Pass ", "PASS  ");

printf "%-10s %-6s %-10s %s\n", "raw", "len", "normalised", "comparisons";

for my $v (@raw) {
    my $n = $v;
    $n =~ s/^\s+|\s+$//g;
    $n = uc $n;

    printf "[%-8s] %-6d [%-8s] raw:%-6s normalised:%s\n",
        $v, length($v), $n,
        ($v eq "PASS" ? "yes" : "no"),
        ($n eq "PASS" ? "yes" : "no");
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
raw        len    normalised comparisons
[PASS    ] 4      [PASS    ] raw:yes    normalised:yes
[pass    ] 4      [PASS    ] raw:no     normalised:yes
[ Pass   ] 6      [PASS    ] raw:no     normalised:yes
[PASS    ] 6      [PASS    ] raw:no     normalised:yes

Four raw spellings, one normalised value, and the comparison went from one match out of four to four out of four.

Now look at rows 1 and 4. They print identically — [PASS ] both times — and one compares equal to "PASS" while the other does not. The only thing distinguishing them in that output is the length column: 4 against 6.

6. Why This Matters in Engineering Work

Counting outcomes. A script that tallies regression results by status will produce separate counts for PASS, pass and PASS if it does not normalise — three rows in the summary for one outcome, and each count too low. The numbers are individually wrong and collectively add up, which is why nobody notices.

Matching a value against a known set. "Is this status one of PASS, FAIL, TIMEOUT" only works if the parsed value is in the same form as the list you are checking against. Normalising on input means that check stays simple.

Comparing names across tools. Two tools reporting the same block as ALU and alu will not join up. Picking one canonical case — and uc is the usual choice for identifiers because it is visibly a normalised form rather than possibly the original — makes the join reliable.

The discipline is where, not how. Normalise at the point you read the value in, once, and then every comparison downstream is against a clean value. Normalising at the point of comparison means remembering to do it at every comparison, and the one you forget is the bug.

Azvya Education Pvt. Ltd.VLSI Mentor
The shape to aim for
my $status = uc trim($raw_field);     # once, on input
...
if ($status eq "PASS") { ... }        # every comparison afterwards is simple

That trim is not a built-in — writing it as a subroutine of your own is Module 10's subject, and it is one of the first genuinely useful ones to write.

7. Common Mistakes

Expecting uc to change the variable. It returns a new string. Assign it back.

Expecting ucfirst to produce title case. It uppercases one character. Use ucfirst(lc $s).

Comparing an untrimmed field. "PASS " is not eq to "PASS", and prints the same.

Assuming \s covers everything invisible. It covers space, tab, newline, carriage return and form feed — not NUL.

Expecting a trim to tidy the interior. It anchors to the start and the end only.

Trusting a debugging print with no length or delimiters. Two different values can print identically, as §5 shows.

Normalising at comparison time instead of input time. You will miss one.

8. Exercise

Exercise 1 — the four functions. With my $s = "fifo_WR"; print all four transformations and then print $s again. Say in one sentence what the last line proves.

Exercise 2 — title case. Given my $s = "DECODER_TOP";, produce Decoder_top. Then explain why ucfirst($s) alone does not, naming what ucfirst actually does.

Exercise 3 — trim and compare. Take my $f = "\tFAIL \n"; and compare it with "FAIL" before and after trimming, printing the length each time. Report both comparison results and both lengths.

Exercise 4 — the interior. Trim " pass with notes " and report the resulting string and its length. Say which spaces survived and why.

Exercise 5 — identify the invisible character. You are given two values that both print as PASS and are not eq to each other. Write a few lines that print the length and the code point of every character in each, and say how you would tell a trailing space from a trailing carriage return.

Exercise 6 — normalise a status list. Build a list containing "PASS", " pass", "Fail", "FAIL " and "fail", normalise each, and count how many normalise to PASS and how many to FAIL. Confirm you get 2 and 3, then say what the counts would have been without normalising.

Exercise 7 — put it on input. Take a script that compares a raw parsed field in three places. Move the normalisation to the single point where the value is first assigned, and say what class of bug that change prevents.

9. Summary

  • uc, lc, ucfirst, lcfirst return a new string and leave the original alone.
  • ucfirst only touches the first character, so it does nothing to an all-caps value. ucfirst(lc $s) is title case.
  • Perl has no built-in trim. The idiom is s/^\s+|\s+$//g, which modifies the variable in place.
  • Trimming anchors to the start and end only — interior whitespace survives.
  • \s covers space, tab, newline, carriage return and form feed, but not NUL.
  • A trailing carriage return from a Windows-written file makes a correct comparison fail, and trimming fixes it.
  • Normalising turned one match out of four into four out of four in §5.
  • A printed value is not evidence. Two different strings can print identically; show the length, delimiters or code points.
  • Normalise on input, once — not at each comparison.

Chapter 4.7 takes the other common shape of input data — one line with separators in it — and covers the pair of functions that turn a line into fields and fields back into a line, including the default behaviour that silently loses an empty trailing column.

Where this fits

Part of the Perl curriculum.