2

I have a rather odd situation. I'm trying to automate the backup of a collection of SVN repositories with Perl. I'm shelling out to the svnadmin dump command, which sends the dump to STDOUT, and any errors it encounters to STDERR.

The command I need to run will be of the form:

svnadmin dump $repo -q >$backupFile

STDOUT will go to the backup file, but, STDERR is what I need to capture in my Perl script.

What's the right way to approach this kind of situation?

EDIT: To clarify: STDOUT will contain the SVN Dump data STDERR will contain any errors that may happen

STDOUT needs to end up in a file, and STDERR needs to end up in Perl. At no point can ANYTHING but the original content of STDOUT end up in that stream or the dump will be corrupted and I'll have something worse than no backup at all, a bad one!

bvr
  • 9,687
  • 22
  • 28
Bart B
  • 661
  • 8
  • 18

5 Answers5

7

Well, there are generic ways to do it within perl too, but the bash solution (which the above makes me think you're looking for) is to redirect stderr first to stdout and then redirect stdout to a file. intuitively this doesn't make a whole lot of sense until you see what's happening internally to bash. But this works:

svnadmin dump $repo -q 2>&1 >$backupFile

However, do not do it the other way (ie, put the 2>&1 at the end), or else all the output of both stdout and stderr will go to your file.

Edit to avoid some people's confusion that this doesn't work:

What you want is this:

# perl -e 'print STDERR "foo\n"; print "bar\n";' 2>&1 > /tmp/f 
foo
# cat /tmp/f
bar

and specifically you don't want this:

# perl -e 'print STDERR "foo\n"; print "bar\n";' > /tmp/f 2>&1
# cat /tmp/f
foo
bar
Wes Hardaker
  • 21,735
  • 2
  • 38
  • 69
  • Good God no!!! The error data cannot get into the file - that would be like throwing random garbage into a MySQL dump, it would be a disaster! – Bart B Feb 03 '12 at 17:48
  • 1
    If you do it the way I quoted above, it **does not** go into the file. – Wes Hardaker Feb 03 '12 at 17:56
  • Looking at that I see STDERR being sent to STDOUT - I guess I'm missing a very important subtlety? – Bart B Feb 03 '12 at 17:58
  • 1
    @BartB: Think of it this way: `fd2 = fd1; fd1 = /tmp/f; ` See how much more sensible that is to read it that way? There’s no problem at all. You should always read fd redirections like that if they confuse you. – tchrist Feb 03 '12 at 20:28
4

Here's one way:

{
    local $/;   # allow reading stderr as a single chunk

    open(CMD, "svnadmin dump $repo -q 2>\&1 1>$backupFile |") or die "...";
    $errinfo = <CMD>;    # read the stderr from the above command
    close(CMD);
}

In other words, use the shell 2>&1 mechanism to get stderr to a place where Perl can easily read it, and use 1> to get the dump sent to the file. The stuff I wrote about $/ and reading the stderr as a single chunk is just for convenience -- you could read the stderr you get back any way you like of course.

andy
  • 6,888
  • 1
  • 19
  • 17
  • Based on that suggestion, would this work? my $error = system("svnadmin dump $repo -q 2>\&1 1>$backupFile"); – Bart B Feb 03 '12 at 17:56
  • 1
    using system() as you suggest would work but the stderr would go the stdout of your script, rather than getting captured as it does in my example. – andy Feb 03 '12 at 18:01
  • Why is the world are you doing this the hard way? – tchrist Feb 03 '12 at 20:33
  • tchrist: because you hadn't yet slapped us in the face with the easier way :-) But seriously, I had been assuming there are errors that the open() or die pattern would catch that the backticks would not. Sounds like I'm wrong about that -- can you confirm? – andy Feb 03 '12 at 21:07
3

While tchrist is certainly correct that you can use handle direction and backticks to make this work, I can also recommend David Golden's Capture::Tiny module. It gives generic interfaces to capturing or tee-ing STDOUT and STDERR, from there you can do with them what you will.

Joel Berger
  • 20,180
  • 5
  • 49
  • 104
1

This stuff is really easy. It’s what backticks were invented for, for goodness’ sake. Just do:

$his_error_output = `somecmd 2>&1 1>somefile`;

and voilà you’re done!

I don’t understand what the trouble is. Didn’t have your gazzintas drilled into you as a young child the way Jethro did? :)

tchrist
  • 78,834
  • 30
  • 123
  • 180
0

From perldoc perlop for qx:

To read both a command's STDOUT and its STDERR separately, it's easiest to redirect them separately to files, and then read from those files when the program is done:

  1. system("program args 1>program.stdout 2>program.stderr");
toolic
  • 57,801
  • 17
  • 75
  • 117