I wrote some benchmark code to test the popular answers, and a method using just pattern match and capture instead of s///. I got good results from my pattern match, so I tried plugging my pattern into Daddio's solution (s-capture), and got a significant speedup of it (s-capture2), but still not as good as just using m//.
use Benchmark 'cmpthese';
use Regexp::Common 'whitespace';
my $f = ' this is a string with spaces to remove ';
cmpthese(-3, {
'Regexp-Common' => sub { $_=$f; s/$RE{ws}{crop}//g; },
'two-s///' => sub { $_=$f; s/^\s+//; s/\s+$//; },
'one-s///' => sub { $_=$f; s/^\s+|\s+$//g; },
's-capture' => sub { $_=$f; s/^\s*(.*?)\s*$/$1/; },
's-capture2' => sub { $_=$f; s/^\s*(\S+(?:\s+\S+)?)?\s*$/$1/; },
'm-capture' => sub { $_=$f; ($_) = /(\S+(?:\s+\S+)?)/; },
});
Rate Regexp-Common s-capture one-s/// s-capture2 m-ca
+pture two-s///
Regexp-Common 659/s -- -91% -93% -95%
+ -96% -97%
s-capture 7002/s 963% -- -21% -45%
+ -59% -73%
one-s/// 8857/s 1244% 26% -- -30%
+ -48% -66%
s-capture2 12699/s 1827% 81% 43% --
+ -26% -51%
m-capture 17179/s 2507% 145% 94% 35%
+ -- -34%
two-s/// 25941/s 3837% 270% 193% 104%
+ 51% --
Not surprisingly, the straightforward method of calling s/// twice is the fastest. What is surprising is how slow Regexp-Common turns out to be, and I was pleasantly surprised at how well m-capture did. If, for some bizarre reason, you needed a single atomic expression to remove leading and trailing whitespace, that would seem to be the way to go.