mill1359 has asked for the wisdom of the Perl Monks concerning the following question:

Monks,

For some reason I can't quite figure, the localizing of the STDIO handles is not being respected by calls to system(). It really seems like this should work.

my $file = "file.txt"; print "before\n"; { local *STDOUT; open (STDOUT, ">$file") or die; system("ls"); } print "after\n";
With only before and after going to the screen and the output of 'ls' going to the file, but it all goes to the screen.

Any thoughts?

P.S. I want to avoid getting the shell involved in my system call, so I don't want to use shell redirection.

Replies are listed 'Best First'.
Re: Localized STDIO and system()
by ikegami (Patriarch) on Jan 30, 2009 at 19:52 UTC
    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.

Re: Localized STDIO and system()
by shmem (Chancellor) on Jan 30, 2009 at 19:49 UTC

    Without investigating why localizing doesn't work, here's something that does the job. See open.

    my $file = "file.txt"; print "before\n"; { open my $oldout, ">&STDOUT" or die "Can't dup STDOUT: $!"; open (STDOUT, ">$file") or die; system("ls"); open STDOUT, ">&", $oldout or die "Can't dup \$oldout: $!"; } print "after\n";
Re: Localized STDIO and system()
by hbm (Hermit) on Jan 30, 2009 at 19:47 UTC
    It works with this change:
    #system("ls"); print `ls`;
      ...which involves the shell.
        No, because special character weren't used.
        $ perl -e'print `ps -f`' UID PID PPID C STIME TTY TIME CMD eric 19557 19556 0 14:46 pts/4 00:00:00 /bin/bash eric 19860 19557 0 15:05 pts/4 00:00:00 perl -eprint `ps -f` eric 19861 19860 0 15:05 pts/4 00:00:00 ps -f

        Notice ps's PPID is perl's PID?

        True, readpipe (backticks) can invoke the shell. Then again, so can system. IPC::System::Simple provides alternatives to system and readpipe that are guaranteed to avoid the shell.