diff --git a/contrib/intarray/bench/bench.pl b/contrib/intarray/bench/bench.pl
index daf3febc80..263cf6ca56 100755
--- a/contrib/intarray/bench/bench.pl
+++ b/contrib/intarray/bench/bench.pl
@@ -100,25 +100,25 @@ if ($opt{e})
 
 my $t0    = [gettimeofday];
 my $count = 0;
-my $b     = $opt{b};
-$b ||= 1;
-my @a;
-foreach (1 .. $b)
+my $opt_b = $opt{b};
+$opt_b ||= 1;
+my @rows;
+foreach (1 .. $opt_b)
 {
-	@a = exec_sql($dbi, $sql);
-	$count = $#a;
+	@rows = exec_sql($dbi, $sql);
+	$count = $#rows;
 }
 my $elapsed = tv_interval($t0, [gettimeofday]);
 if ($opt{o})
 {
-	foreach (@a)
+	foreach (@rows)
 	{
 		print "$_->{mid}\t$_->{sections}\n";
 	}
 }
 print sprintf(
 	"total: %.02f sec; number: %d; for one: %.03f sec; found %d docs\n",
-	$elapsed, $b, $elapsed / $b,
+	$elapsed, $opt_b, $elapsed / $opt_b,
 	$count + 1);
 $dbi->disconnect;
 
diff --git a/src/backend/parser/check_keywords.pl b/src/backend/parser/check_keywords.pl
index 702c97bba2..68d1f517b7 100644
--- a/src/backend/parser/check_keywords.pl
+++ b/src/backend/parser/check_keywords.pl
@@ -21,8 +21,8 @@ sub error
 	return;
 }
 
-$, = ' ';     # set output field separator
-$\ = "\n";    # set output record separator
+local $, = ' ';     # set output field separator
+local $\ = "\n";    # set output record separator
 
 my %keyword_categories;
 $keyword_categories{'unreserved_keyword'}     = 'UNRESERVED_KEYWORD';
diff --git a/src/test/locale/sort-test.pl b/src/test/locale/sort-test.pl
index b61968b7e0..5efafd6e20 100755
--- a/src/test/locale/sort-test.pl
+++ b/src/test/locale/sort-test.pl
@@ -8,7 +8,7 @@ open(my $in_fh, '<', $ARGV[0]) || die;
 chop(my (@words) = <$in_fh>);
 close($in_fh);
 
-$" = "\n";
+local $" = "\n";
 my (@result) = sort @words;
 
 print "@result\n";
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 1d5450758e..b55823f356 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1254,7 +1254,7 @@ END
 		$node->clean_node if $exit_code == 0 && TestLib::all_tests_passing();
 	}
 
-	$? = $exit_code;
+	$? = $exit_code; ## no critic (RequireLocalizedPunctuationVars)
 }
 
 =pod
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index 1a92ed233a..f5b90261f5 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -45,7 +45,7 @@ sub lcopy
 
 sub Install
 {
-	$| = 1;
+	local $| = 1;
 
 	my $target = shift;
 	$insttype = shift;
@@ -762,13 +762,10 @@ sub read_file
 {
 	my $filename = shift;
 	my $F;
-	my $t = $/;
-
-	undef $/;
+	local $/ = undef;
 	open($F, '<', $filename) || die "Could not open file $filename\n";
 	my $txt = <$F>;
 	close($F);
-	$/ = $t;
 
 	return $txt;
 }
diff --git a/src/tools/msvc/Project.pm b/src/tools/msvc/Project.pm
index d90a996d46..20f79b382b 100644
--- a/src/tools/msvc/Project.pm
+++ b/src/tools/msvc/Project.pm
@@ -420,13 +420,10 @@ sub read_file
 {
 	my $filename = shift;
 	my $F;
-	my $t = $/;
-
-	undef $/;
+	local $/ = undef;
 	open($F, '<', $filename) || croak "Could not open file $filename\n";
 	my $txt = <$F>;
 	close($F);
-	$/ = $t;
 
 	return $txt;
 }
@@ -435,15 +432,12 @@ sub read_makefile
 {
 	my $reldir = shift;
 	my $F;
-	my $t = $/;
-
-	undef $/;
+	local $/ = undef;
 	open($F, '<', "$reldir/GNUmakefile")
 	  || open($F, '<', "$reldir/Makefile")
 	  || confess "Could not open $reldir/Makefile\n";
 	my $txt = <$F>;
 	close($F);
-	$/ = $t;
 
 	return $txt;
 }
diff --git a/src/tools/perlcheck/perlcriticrc b/src/tools/perlcheck/perlcriticrc
index 4550928319..4130da460a 100644
--- a/src/tools/perlcheck/perlcriticrc
+++ b/src/tools/perlcheck/perlcriticrc
@@ -23,6 +23,14 @@ verbose = %f: %m at line %l, column %c.  %e.  ([%p] Severity: %s)\n
 # allow octal constants with leading zeros
 [-ValuesAndExpressions::ProhibitLeadingZeros]
 
+# Require 'local' declarations for assignments to perl magic variables,
+# but don't require local declarations for assignments to %ENV and %SIG, even
+# though many should be local, especially for %ENV.
+# Note: perlcritic doesn't like things like this, even though it's safe:
+#   local %ENV = %ENV; $ENV{foo} = 'bar';
+[Variables::RequireLocalizedPunctuationVars]
+allow = %ENV %SIG
+
 # severity 4 policies currently violated
 
 [-BuiltinFunctions::RequireBlockGrep]
@@ -38,7 +46,6 @@ verbose = %f: %m at line %l, column %c.  %e.  ([%p] Severity: %s)\n
 [-ValuesAndExpressions::ProhibitCommaSeparatedStatements]
 [-ValuesAndExpressions::ProhibitConstantPragma]
 [-ValuesAndExpressions::ProhibitMixedBooleanOperators]
-[-Variables::RequireLocalizedPunctuationVars]
 
 # severity 3 policies currently violated
 
diff --git a/src/tools/win32tzlist.pl b/src/tools/win32tzlist.pl
index 25f7efbc58..97484016bb 100755
--- a/src/tools/win32tzlist.pl
+++ b/src/tools/win32tzlist.pl
@@ -60,12 +60,13 @@ $basekey->Close();
 # Fetch all timezones currently in the file
 #
 my @file_zones;
+my $pgtz;
 open(my $tzfh, '<', $tzfile) or die "Could not open $tzfile!\n";
-my $t = $/;
-undef $/;
-my $pgtz = <$tzfh>;
+{
+	local $/ = undef;
+	$pgtz = <$tzfh>;
+}
 close($tzfh);
-$/ = $t;
 
 # Attempt to locate and extract the complete win32_tzmap struct
 $pgtz =~ /win32_tzmap\[\] =\s+{\s+\/\*[^\/]+\*\/\s+(.+?)};/gs
