Error during reading output of "svn log" command - perl

There is "svn: Write error" in case if I try to execute following script.
But if I disable line with "last" there are no any errors.
Why?
#!/usr/bin/perl
my $repos = $ARGV[0];
my $rev = $ARGV[1];
my $repoURL = "file:///" . $ARGV[0];
open (SVNLOG, "svn log -r $rev -v $repoURL | ");
while (my $line = <SVNLOG>) {
last;
}
close(SVNLOG);

Because the svn process is trying to write to a stream (its standard output), and you closed the stream before it could finish. If you don't want to see this error line, you will have to redirect svn standard error to /dev/null, or make sure you read all of its output before closing the file handle.

Not a direct answer you your question, but you should consider using SVN::Client from CPAN instead of using the svn command line tool directly.

Related

Handling Perforce message in Perl when there are no new files submitted

I am trying to code a Perl subroutine that returns an array of files that has been modified and submitted to the Perforce repository from $previous_date until now. This is how the subroutine looks like:
sub p4_files {
my ($previous_date) = #_;
my $files = "//depot/project/design/...rtl.sv"
my $p4cmd = "p4 files -e $files\#$previous_date,\#now";
my #filelist = `$p4cmd`;
chomp #filelist;
return #filelist;
}
The subroutine works as expected if there are files submitted between the given dates. However, it happens that no new changes are made, and executing the p4 files command returns a message instead:
prompt% p4 files -e //depot/project/design/...rtl.sv\#25/05/2017,\#now
prompt% //depot/project/design/...rtl.sv\#25/05/2017,\#now - no revision(s) after that date.
How should I handle this in my Perl script? I would like to exit the script when such a situation is encountered.
Unfortunately, p4 returns exit code 0 regardless of whether it finds some files or whether it returns the "no revision(s) after that date" message. That means you have to parse the output.
The simplest solution is probably to exit the script if $filelist[0] =~ / - no revision\(s\) after that date\./. The downside is we don't know how "stable" that message is. Will future versions of Perforce emit this message exactly, or is it possible they would reword?
Another option is to use the -s switch: my $p4cmd = "p4 -s files -e $files\#$previous_date,\#now";. That causes p4 to prepend the "severity" before every line of output. If a file is found, the line will start with info:, while the "no revision(s) after that date" will start with error:. That looks a bit more stable to me: exit if grep /^error:/, #filelist. Watch out for the last line; when you use the -s switch, you get an extra line with the exit code.
Yet another option would be to use P4Perl. In that case you'd get the results as structured data, which will obviate the parsing. That's arguably the most elegant, but you'd need the P4Perl module first.
I suggest using the -F flag to tame the output:
my $p4cmd = "p4 -F %depotFile% files -e $files\#$previous_date,\#now";
and then go ahead with the:
my #filelist = `$p4cmd`;
good_bye() unless #filelist; # Say goodbye and exit.
#filelist will be empty if there are no lines of output containing a %depotFile% field, and now your caller doesn't need to try to parse the depot path out of the standard p4 files output.
If you want to massage the p4 files output further, take a look at p4 -e files (args) so you can see what the different fields are that you can plug into -F.
Just do nothing if the array isn't populated.
my #filelist = `$p4cmd`;
good_bye() unless #filelist; # Say goodbye and exit.
chomp #filelist;
To suppress the message, just redirect stderr of the command to a bitbucket:
my $p4cmd = "p4 files -e $files\#$previous_date,\#now 2> /dev/null";

How to get STDERR in Perl from a command executed in pipe with su -c

I'm trying to capture the output of the command executed as a different user using:
my $command = qq(sudo su - <username> -c '/usr/bin/whatever');
my $pid = open $cmdOutput, "-|", $command;
How can I capture the STDERR of /usr/bin/whatever?
I tried
$pid = open $cmdOutput, "-|", $command || die " something went wrong: $!";
but it looks like this is capturing the possible errors of "open" itself.
I also tried
my $command = qq(sudo su - <username> -c '/usr/bin/whatever' 2>/tmp/error.message);
which will redirect the STDERR to the file, which I can parse later, but I wanted some more straightforward solution.
Also, I only want to use core modules.
This is covered thoroughly in perlfaq8. Since you are using a piped open, the relevant examples are those that go by open3 from the core IPC::Open3 module.
Another option is to use IPC::Run for managing your processes, and the pump function will do what you need. The IPC::Open3 documentation says for IPC::Run
This is a CPAN module that has better error handling and more facilities than Open3.
With either of these you can manipulate STDOUT and STDERR separately or together, as needed. For convenient and complete output capture also see Capture::Tiny.
Other than 2>output redirection, there are no more elementary methods for the piped open.
If you don't mind mixing the streams or losing STDOUT altogether, another option is
my $command = 'cmd 2>&1 1>/dev/null' # Remove 1>/dev/null to have both
my $pid = open my $cmdOutput, "-|", $command;
while (<$cmdOutput>) { print } # STDERR only
The first redirection merges STDERR stream with STDOUT so you get them both, and mixed (with STDOUT subject to buffering, thus things may well come out of order). The second redirect sends the STDOUT away so with it in place you read only the command's STDERR from the handle.
The question is about running an external command using open but I'd like to mention that the canonical and simple qx (backticks) can be used in the same way. It returns the STDOUT so redirection just like above is needed to get STDERR. For completeness:
my $cmd = 'cmd_to_execute';
my $allout = qx($cmd 2>&1); # Both STDOUT and STDERR in $out, or
my $stderr = qx($cmd 2>&1 1>/dev/null); # Only STDERR
my $exit_status = $?;
The qx puts the child process exit code (status) in $?. This can then be inspected for failure modes; see a summary in the qx page or a very thorough discussion in I/O operators in perlop.
Note that the STDERR returned this way is from the command, if it ran. If the command itself couldn't be run (for a typo in command name, or fork failed for some reason) then $? will be -1 and the error will be in $!.
As suggested by zdim I used the IPC::Open3 module for the matter and I've got something like this doing the job for me
$instanceCommand = qq(sudo su - <username> -c '<command>');
my ($infh,$outfh,$errfh,$pid);
$errfh = gensym();
$pid = open3($infh, $outfh, $errfh, $instanceCommand);
my $sel = new IO::Select;
$sel->add($outfh,$errfh);
while (my #ready = $sel->can_read){
foreach my $fh (#ready){
my $line =<$fh>;
if (not defined $line){
$sel->remove($fh);
next;
}
if ($fh == $outfh){
chomp($line);
#<----- command output processing ----->
}
elsif ($fh == $errfh){
chomp $line;
#<----- command error processing ----->
}
else {
die "Reading from something else\n";
}
}
}
waitpid($pid, 0);
Maybe not completely bullet proof, but its working fine for me. Even whilst executing funny cascaded script as < command > .
The desired destination, opened for writing, could be dup()'ed to FD #2

How to read STDOUT from a sub-process in OO Perl

In Perl, one way to read the STDOUT of a subprocess is to use open:
open(PIPE, "ls -l |");
I was looking for a more object-oriented way to do this, though, and I've been using IO::Pipe with some success. I want to detect errors, though, specifically if the command is not executable. I can't figure out how to do that via IO::Pipe, though. Here's what I have:
use strict;
use warnings;
use IO::Pipe;
my($cmd) = join (" ", #ARGV);
open(PIPE, "$cmd |") || die qq(error opening PIPE);
while (<PIPE>) {
chomp;
print "DBG1: $_\n";
}
close PIPE;
my($pipe) = IO::Pipe->new();
$pipe->reader($cmd);
die qq(error opening IO::Pipe) if $pipe->eof();
while (<$pipe>) {
chomp;
print "DBG2: $_\n";
}
$pipe->close();
If the sub-process command is invalid, both checks will correctly die. If the sub-process produces no output, though, eof() will report an error, even if the command itself is fine:
$ perl pipe.pl "ls -l >/dev/null"
error opening IO::Pipe at pipe.pl line 20.
A bunch of questions, then:
Is there a reasonable OO way to read from a sub-process in Perl? Is IO::Pipe the correct tool to use? If so, how do I check to make sure the sub-process command is created successfully? If not, what should I be using? I don't want to write to the sub-process, so I don't think I want IPC::Open2 or IPC::Open3. I would prefer to use a core module, if possible.
The issue is not IO::Pipe. The problem is eof is the wrong way to check for a pipe error. It doesn't mean there's no pipe, it means there's nothing to read from that pipe. You'd have the same problem with eof PIPE. It's perfectly fine for a sub-process to not print anything.
If you want to check the sub-process successfully ran, it turns out IO::Pipe already does that for you.
# IO::Pipe: Cannot exec: No such file or directory
$pipe->reader("hajlalglagl");
Backticks is not a core module but seems to do what your looking for.
use strict;
use warnings;
use Backticks;
my $cmd = Backticks->new(join (" ", #ARGV));
$cmd->run();
if ($cmd->success){
print $cmd->stdout
} else {
print "Command failed\n";
}
Running this with a valid command then invalid command gives the below results
io_pipe.pl "uname -o"
GNU/Linux
io_pipe.pl "uname -z"
Command failed
Update
As pointed out by #thisSuitIsNotBlack, this module changes the deafult behaviour of backticks in perl. You should read the Notes section of the POD. However the main one to be aware of is:
The overriding of backticks is provided by Filter::Simple. Source
filtering can be weird sometimes... if you want to use this module in
a purely traditional Perl OO style, simply turn off the source
filtering as soon as you load the module:
use Backticks;
no Backticks;

Dump a log message from tortoise svn to a file using TortoiseProc.exe

I was trying to create a script that could parse the log for a particular revision in svn and get details from that log like the author name, the underlying paths affected and the actions performed. I am trying to use the following command in perl with backticks:
TortoiseProc.exe /command:log /path:"$svn_path" /startrev:"$svn_rev" /endrev:"$svn_rev" /outfile:$file;
for the outfile option it's been specified as /outfile:path\to\file. I've tried so many combinations to get the syntax right but the log file is not getting created. I tried to do
/outfile:D:\other\to\filename.txt
I even tried putting only filename there thinking it'd be created in the same directory, tried only giving the path and even tried creating the file myself waiting for it to write to. Nothing works. Any help would be greatly appreciated.
The backslash in Perl and in most of shells needs to be properly qouted.
Check the cmd.log file and tune the amount of backslashes until it is working
my $file = "D:\\\\other\\\\to\\\\filename.txt";
open my $fh,'>','cmd.log' or die $!;
print $fh "TortoiseProc.exe /command:log /path:\"$svn_path\" /startrev:\"$svn_rev\" /endrev:\"$svn_rev\" /outfile:\"$file\"\r\n";
close($fh);
my $out = `TortoiseProc.exe /command:log /path:"$svn_path" /startrev:"$svn_rev" /endrev:"$svn_rev" /outfile:"$file"`;
The outfile argument only works if you are using a nightly build of TortoiseSVN. See here
An alternative might be to use svn.exe, which also comes with TortoiseSVN, in the following way:
svn.exe log -v -r 111041 >output.txt

How to redirect SVN stderrs to /dev/null using perl

I have a script to check if any data is available on svn repo path but not added into svn. It works fine for me but this gives stderr for adding and sending files like below;
Adding 1/a
Sending 1/a
Transmitting file data ...........
Committed revision 529.
Code:
use strict;
use warnings;
sub notAdded {
my #svnstatus = `svn st`;
foreach my $status (#svnstatus) {
chomp($status);
if ($status =~ m/^?/) {
my ($symble, $left) = split(' ', $status);
system("svn add $left");
}
}
}
&notAdded();
system("svn commit -m 'comment'");
Can anyone please suggest me how can I redirect this error to /dev/null within the script.
The normal way to hide unwanted output with SVN is to use the -q (quiet) flag:
svn -q add nothere
displays nothing.
Or the really easy way:
system("svn add $left 2>/dev/null");