Skip to content
VLSI Mentor

Perl Part I · Module 4 · Strings and Text

Splitting and Joining

These two functions do most of the real work in text handling: one takes a line with separators in it and gives you a list of fields, the other takes a list of fields and gives you back a line. Together they are how a script reads a comma-separated report, changes one column, and writes it out again. That round trip is also where the chapter's warning lives, because by default splitting discards empty fields at the end of a line — so a row ending in an empty column comes back shorter than it went in, with no warning and no error. There is a second trap of the same kind: the separator written as a quoted space and the separator written as a one-character pattern look nearly identical in source and differ by five fields on the same input.

Foundation16 min readPerlPart IFundamentalsStringsParsingDebugging

Part I · Module 4 · Chapter 4.7 · Splitting and Joining

1. The Ordinary Case

Azvya Education Pvt. Ltd.VLSI Mentor
basic-split.pl
use strict;
use warnings;

my $line = "alu,smoke,PASS,184";

my @fields = split /,/, $line;

print scalar(@fields), " fields\n";
print join(" | ", @fields), "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
4 fields
alu | smoke | PASS | 184

split takes a separator and a string, and returns the pieces between the separators. join takes a separator and a list, and returns one string with that separator between the pieces.

They are opposites, and on tidy data the round trip is exact:

Azvya Education Pvt. Ltd.VLSI Mentor
round-trip.pl
use strict;
use warnings;

my $line = "alu,smoke,PASS,184";

my @fields = split /,/, $line;
my $back   = join ",", @fields;

print "original : $line\n";
print "rebuilt  : $back\n";
print "identical: ", ($back eq $line ? "yes" : "no"), "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
original : alu,smoke,PASS,184
rebuilt  : alu,smoke,PASS,184
identical: yes

That is the shape of nearly every parsing script: split the line, work with the fields, join them back. The rest of this chapter is about the cases where the round trip is not exact.

2. Trailing Empty Fields Are Discarded

Here is the behaviour to know.

Azvya Education Pvt. Ltd.VLSI Mentor
trailing-empty.pl
use strict;
use warnings;

my $line = "a,b,,";

my @default = split /,/, $line;
my @kept    = split /,/, $line, -1;

printf "no limit : %d fields -> [%s]\n", scalar(@default), join("][", @default);
printf "limit -1 : %d fields -> [%s]\n", scalar(@kept),    join("][", @kept);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
no limit : 2 fields -> [a][b]
limit -1 : 4 fields -> [a][b][][]

The line "a,b,," has four fields: a, b, an empty one, and another empty one. The default split returns two.

The two empty fields at the end were discarded. Passing -1 as a third argument — the limit — keeps them.

Which breaks the round trip

Azvya Education Pvt. Ltd.VLSI Mentor
broken-round-trip.pl
use strict;
use warnings;

my $line = "a,b,,";

my @fields = split /,/, $line;
my $back   = join ",", @fields;

print "original : [$line]\n";
print "rebuilt  : [$back]\n";
print "identical: ", ($back eq $line ? "yes" : "no"), "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
original : [a,b,,]
rebuilt  : [a,b]
identical: no

The rebuilt line is two characters shorter. Without the brackets around each value — and without the identical line — that difference is easy to miss entirely, which is why both are in the script.

In a real script neither is printed, and the shortened line simply goes into the output file.

3. A Leading Empty Field Is Kept

The behaviour is not symmetric, and knowing that stops the other case looking like a bug:

Azvya Education Pvt. Ltd.VLSI Mentor
leading-empty.pl
use strict;
use warnings;

my @fields = split /,/, ",a,b";

printf "%d fields -> [%s]\n", scalar(@fields), join("][", @fields);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
3 fields -> [][a][b]

Three fields, the first one empty. So the rule is specifically about the end of the string: leading and interior empty fields are preserved, trailing ones are dropped unless you ask for them.

That asymmetry is deliberate rather than accidental — it makes the common case of a line with a trailing separator behave tidily — but it is still a surprise the first time a column count comes up short.

4. The Round Trip, Drawn

The same row, split two ways, rebuilt two ways

A fork with five blocks. At the middle left is the source row, the five characters a comma b comma comma, which contains four fields of which the last two are empty. Two paths lead from it. The upper path is the default split with no limit argument: it discards the trailing empty fields and returns only two fields, so joining them back produces the three-character line a comma b, and the original data is lost. The lower path is split with a limit of minus one: it keeps all four fields including the two empty ones, so joining them back reproduces the original five-character row exactly. Neither path warns and both exit zero.Source row a,b,,4 fields, last 2 emptysplit /,/ no limitdrops trailing emptiesjoin gives a,b2 chars short — lossysplit /,/ limit -1keeps all four fieldsjoin gives a,b,,identical to sourcedefaultlimit -112
Both paths run without a warning and both exit zero. The upper path returns a shorter list, so the rebuilt line is two characters shorter than the original — which is the entire visible symptom. The limit argument on the lower path is the only difference between faithful and lossy.

5. split " " Is Not split / /

The second trap, and the two forms are a single character apart in source.

Azvya Education Pvt. Ltd.VLSI Mentor
space-split.pl
use strict;
use warnings;

my $line = "  alu   smoke  PASS ";

my @magic   = split " ",  $line;
my @literal = split / /,  $line;

printf "split \" \" : %d fields -> [%s]\n", scalar(@magic),   join("][", @magic);
printf "split / /  : %d fields -> [%s]\n", scalar(@literal), join("][", @literal);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
split " " : 3 fields -> [alu][smoke][PASS]
split / /  : 8 fields -> [][][alu][][][smoke][][PASS]

Three fields against eight, from the same line.

  • split " " — the separator written as a quoted string containing one space — is a documented special case. It means "split on runs of whitespace, and ignore any whitespace at the start of the line". It is the right tool for whitespace-separated text with irregular spacing, which is most tool output.
  • split / / — the separator written as a pattern matching one space — means exactly that. Three consecutive spaces are three separators with empty fields between them, and leading spaces produce leading empty fields.

So for a line of whitespace-aligned columns, split " " gives you the fields and split / / gives you the fields plus a lot of empty strings. The difference is invisible at a glance and it is why the quoted form exists.

Note that split /\s+/ behaves like split / / regarding the leading whitespace — it collapses runs but still gives you an empty first field when the line starts with a space. Only the literal " " form discards the leading whitespace. That is the detail that makes " " worth remembering as its own thing rather than as shorthand for a pattern.

6. The Limit Argument Has a Second Use

A positive limit stops splitting after that many fields and leaves the remainder intact:

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

my $line = "alu,smoke,PASS,184";

my @two = split /,/, $line, 2;

printf "%d fields -> [%s]\n", scalar(@two), join("][", @two);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
2 fields -> [alu][smoke,PASS,184]

That is genuinely useful: a log line of the form key: everything else, including: colons splits correctly with a limit of 2, where an unlimited split would break the value apart at every colon.

To summarise the three forms of the third argument:

LimitEffect
omittedsplit everything, discard trailing empty fields
-1split everything, keep trailing empty fields
a positive nreturn at most n fields; the last one holds the unsplit remainder

7. Two More Forms Worth Knowing

An empty pattern splits into characters:

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

print join("|", split //, "alu"), "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
a|l|u

join takes any separator, including an empty one or a multi-character one:

Azvya Education Pvt. Ltd.VLSI Mentor
join-forms.pl
use strict;
use warnings;

my @parts = ("alu", "smoke", "01");

print join("_",    @parts), "\n";
print join("",     @parts), "\n";
print join(" -> ", @parts), "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
alu_smoke_01
alusmoke01
alu -> smoke -> 01

join is also the readable way to build a separator-delimited line, and it is better than concatenating in a loop because there is no trailing-separator problem to handle.

8. A Worked Example

Reading a report row, changing one field, writing it back — done faithfully:

Azvya Education Pvt. Ltd.VLSI Mentor
update-row.pl
use strict;
use warnings;

my $row = "alu_smoke_01,PASS,184,";

my @fields = split /,/, $row, -1;

printf "read %d fields\n", scalar(@fields);

$fields[1] = "FAIL";

my $out = join ",", @fields;

print "in  : [$row]\n";
print "out : [$out]\n";
printf "same field count: %s\n", (scalar(@fields) == 4 ? "yes" : "no");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
read 4 fields
in  : [alu_smoke_01,PASS,184,]
out : [alu_smoke_01,FAIL,184,]
same field count: yes

Four fields read, four written, and the trailing empty column survived. The -1 is the only thing making that true — drop it and the same script reads three fields and writes alu_smoke_01,FAIL,184, with the final comma gone.

Indexing into the array with $fields[1] is Module 5's syntax; it appears here because the read-modify-write shape is the whole point of the chapter and cannot be shown without it.

9. Why This Matters in Engineering Work

Parsing a delimited report is the single most common scripting task in this trade. A regression summary, a timing report exported as CSV, a coverage table — all of them are split on input.

Read-modify-write is where the default bites. Any script that rewrites a file it read is at risk, and the risk is concentrated on exactly the rows where a value was missing. A timing report whose slack column is empty for unconstrained paths will lose that column from precisely those rows.

Irregular whitespace is the normal case in tool output. Columns aligned by padding, not by a delimiter. split " " handles it; split / / produces a list full of empty strings that then fail to compare against anything. This is a frequent cause of "field 3 is empty and I don't know why".

Separators that are metacharacters. Splitting a version on . or a path on | without escaping gives nonsense, because the pattern argument is a regex. The callout in §1 is the whole warning.

Two habits cover all of it:

  • Split with -1 whenever the data will be written back.
  • Count the fields and check the count before you index into them. A row with fewer fields than expected is a data problem you want to report, not one you want to discover through an undefined value three functions later.

10. Common Mistakes

Letting the default drop trailing empty fields. Use -1 for any round trip. §2 measures the loss.

Assuming empty fields are dropped generally. Only trailing ones are; leading and interior ones are kept.

Writing split / / for whitespace-separated columns. Use split " ". §5 is three fields against eight.

Expecting split /\s+/ to discard leading whitespace. Only the literal " " form does.

Forgetting the pattern is a regex. split /./ splits on every character. Escape the separator or use \Q.

Indexing a field without checking the count. A short row gives undef, which then interpolates as nothing — Chapter 4.2 §6.

Building a delimited line by concatenating in a loop. join has no trailing-separator problem.

11. Exercise

Exercise 1 — the basic round trip. Split "alu,smoke,PASS,184" on commas, report the field count, join it back and confirm the result is identical to the original.

Exercise 2 — lose the data, then keep it. Run the §2 script. Record both field counts. Then rebuild each list with join and report which one reproduces "a,b,," exactly.

Exercise 3 — the asymmetry. Split ",a,b," with no limit and then with -1. Report both counts and say exactly which empty fields survived in each, then state the rule in one sentence.

Exercise 4 — three against eight. Run the §5 script. Then write one sentence explaining why split / / produced two empty fields at the start, and say which form you would use for a line of space-aligned tool output.

Exercise 5 — the metacharacter. Split "v1.2.3" with split /./ and report how many fields you get. The answer is surprising — explain it using §2's rule about trailing empty fields. Then do it correctly and say what one character you changed.

Exercise 6 — a limit of 2. Given my $line = "ERROR: setup violation on path: U1/U2";, use a limit to split it into exactly two fields: the label and the whole rest of the message. Confirm the second field still contains its colon.

Exercise 7 — the faithful update. Write a script that reads "fifo_wr,PASS,92,", changes the second field to FAIL, and writes the row back out with the trailing empty field intact. Then remove the -1 and record how the output row differs.

12. Summary

  • split turns a line into fields; join turns fields into a line. On tidy data the round trip is exact.
  • The first argument to split is a regular expression, so a separator that is a metacharacter must be escaped.
  • Trailing empty fields are discarded by default. "a,b,," gives two fields, which silently shortens any line you write back.
  • A limit of -1 keeps them, and is the right default whenever the data will be rewritten.
  • A leading empty field is kept — the rule is about the end of the string only.
  • split " " is a special case meaning "runs of whitespace, leading whitespace discarded". split / / is one literal space, and gave eight fields where " " gave three.
  • A positive limit returns at most that many fields, with the remainder unsplit in the last one.
  • split // splits into characters, and join accepts any separator.
  • Count your fields before indexing them.

Chapter 4.8 covers the other half of producing a report — placing values into a template with fixed widths and a set number of decimals, so that the columns this chapter splits can be written out aligned.

Where this fits

Part of the Perl curriculum.