in reply to Localized STDIO and system()

local backups the variable being localized and creates a new one. The old one still exists anonymously. That means the original STDOUT still has file descriptor one open. open uses the first available file descriptor — probably three here — and associates it with STDOUT.
#!/usr/bin/perl -l my $file = "file.txt"; print "before"; { print STDERR "fileno before local: ", fileno(STDOUT); local *STDOUT; open (STDOUT, '>', $file) or die; print STDERR "fileno after open: ", fileno(STDOUT); system("ls"); } print "after";
before fileno before local: 1 fileno after open: 3 740300.pl after

ls takes fd0 as its STDIN, fd1 as its STDOUT and fd2 as its STDERR, which are inherited from the Perl script and unchanged by the local *STDOUT;. You need to change where fd1 points, not where STDOUT points.

#!/usr/bin/perl -l my $file = "file.txt"; print "before"; { print STDERR "fileno before local: ", fileno(STDOUT); open (my $old_stdout, '>&', *STDOUT) or die; close (STDOUT); open (STDOUT, '>', $file) or die; print STDERR "fileno after open: ", fileno(STDOUT); system("ls"); close (STDOUT); open (STDOUT, '>&', $old_stdout) or die; } print "after";
$ perl 740300.pl before fileno before local: 1 fileno after open: 1 after $ cat file.txt 740300.pl

It might be easier just to use open2 or open3. Just pass them the handles and they'll do the duping (>&) for you.

#!/usr/bin/perl -l use IPC::Open3 qw( open3 ); my $file = "file.txt"; print "before"; { open (my $fh, '>', $file) or die; my $pid = open3('<&STDIN', '>&'.fileno($fh), '>&STDERR', 'ls'); waitpid($pid, 0); } print "after";

Update: Fixed some errors in the fd numbers.