ZSH Magic for certain exit-status situations - perl

Hey I thought about writing a function that prompts me in certain situations (perl had a bad exit status and the warning it prints starts with "Could not find * in #INC) with maybe re-running the command.
Do you think this is feasable?
I have found that TRAPZERR function. but i don't even get the name of the last command in there, only the last argument, so one concrete point would be: how can I get the command-name out of it? !!:0 doesn't work.
Next thing is: i think i might have to do some tricks with preexec to catch the first line of the stderr (if that doesn't work that's not a big problem, but it would be interesting anyway), I thought about appending an 2> to a pipe or something but I'm not sure how to get that pipe constructed in the first place. But as stated above, this is a minor issue.
But really, if you think: No this task is impossible! Please tell me so (although I would be interested why).
From the one answer i got until now I think i can narrow down the problem a little:
Is it possible to wrap a MULTIOS redirection around a perl script? ( I thought about some special kind of exec, but came up with none that worked)

Here's a start.
preexec() {
lastcommand=$1
exec 3>&1 4>&2
exec 2> >(tee /tmp/output 1>&4)
}
precmd() {
read line </tmp/output
case "$line" in
"Can't locate"*)
echo "Perl module missing running $lastcommand"
;;
esac
}
But maybe you want to do something at the language level like How can I hook into Perl's use/require so I can throw an exception?

Related

What does 'echo' do within a perl variable declaration?

I am working on transcribing an outdated file from perl to python and got caught up with some perl syntax.
my $jobID = `echo \$JOBID`;
chomp($jobID);
unless ($jobID) {
print "Please specify a job\n";
exit;
}
Thus far, I have been able to transcribe all of the command-line parsing extremely easily but am very fairly stuck here with what exactly this code is doing (specifically the echo within the declaration on line 1).
Within the perl script cmd-line parsing options - that enables one to set the jobID - it states that "default = $JOBID". So my assumption is that the first line in this code simply sets this default jobID until one is specified.
If this is the case why would you need to use echo within the variable default declaration? Is this good practice in perl or am I missing a larger picture?
I have tried searching low and high for this but can't seem to google ninja my way to anything useful.
Any help on the 'echo' would be greatly appreciated (or any good reads on this as well)!
This is one way to get a value from a shell variable. The backticks (`) run the shell command and give you the output. So the echo is running inside of a shell and in this case it just returns the one shell variable. A cleaner way to get this in Perl is to use %ENV like so:
my $jobID = $ENV{'JOBID'};
This also removes the need for chomp, avoids creating an extra process, and is much more efficient.
It is evaluating an environment variable named $JOBID and storing the result in $jobID, which (as duskwuff points out) is better accomplished using $ENV{JOBID}.
The backticks around the echo \$JOBID tell Perl to invoke the specified command in a subshell and return the output of the invoked command.

Perl: After a successful system call, "or die" command still ends script

I am using the following line to make a simple system call which works:
system ("mkdir -p Purged") or die "Failed to mkdir." ;
Executing the script does make the system call and I can find a directory called Purged, but the error message is still printed and the script dies. What is wrong with my syntax?
system returns the exit status of the command it calls. In shell, zero exit status means success. You have to invert the logic:
0 == system qw(mkdir -p Purged) or die "Failed to create the dir\n";
That would be a little confusing, wouldn't? - Leonardo Herrera on Ikegami's answer
Yes, it is confusing that the system command inverts true and false in Perl, and creates fun logic like this:
if ( system qw($command) ) {
die qq(Aw... If failed);
}
else {
say qq(Hooray! It worked!);
}
But, it's understandable why the system command does this. In Unix, an exit status of zero means the program worked, and a non-zero status could give you information why your system call failed. Maybe the program you were calling doesn't exist. Maybe the program sort of worked as expected. For example, grep returns an exit code of 1 when grep works, but there were no matching lines. You might want to distinguish when grep returns zero, one, or a return code greater than one. (Yes, I know it's silly to use a system call to grep in a Perl program, but that's the first example I could think of).
To prevent casual confusion, I create a variable that holds the exit status of my system command instead of testing the output of system directly:
my $error = system qw($command);
if ($error) {
die qq(Aw... It failed);
}
else {
say qq(Hooray! It worked!);
}
It's completely unnecessary, and people who work with Perl should know that system reverses Perl's definition of true and false, but if I hadn't had my coffee in the morning, I may miss it as I go over someone else's code. Doing this little step just makes the program look a bit more logical.
The Perldoc of system give you code that allows you to test the output of your system command to see exactly what happened. (If there was an error, or a system interrupt signal killed the system call). It's nice to know if you need to interrogate your system return value to figure out what went wrong.
system returns 0 on success, so you want and rather than or.
See also: use autodie qw( system );
To add what hasn't been mentioned but what comes with it and can quietly mess things up.
The system's return of 'zero-or-not' only tells whether the command itself executed successfully, by shell or execvp; the 0 does not mean that the command succeeded in what it was doing.†
In case of non-zero return you need to unpack $? for more information;† see system for how to do this. For one, the command's actual exit code is $? >> 8, what the executed program was designed to communicate at its exit.
Altogether you may want to do something to the effect of
sub run_system {
my ($cmd, #other_agrs) = #_; # may do more via #other_args
my ($cmdref, $sys_ret) = (ref $cmd, 0); # LIST or scalar invocation?
if ($cmdref eq 'ARRAY') {
$sys_ret = system(#$cmd);
}
elsif (not $cmdref) {
$sys_ret = system($cmd);
}
else { Carp::carp "Invocation error, got $cmdref" }
return 1 if $sys_ret == 0;
# Still here? The rest is error handling.
# (Or handling of particular non-zero returns; see text footnote)
Carp::carp "Trouble with 'system($cmd)': $?";
print "Got exit " . ($? >> 8) . " from $cmd\n";
return 0; # or Carp::croak (but then design changes)
}
This can be done merely as system == 0 or do { ... }; or such right in the code, but this way there's a little library, where we can refine error handling more easily, eventually decide to switch to a module to manage external commands, etc. Also, being a user-level facility now it makes sense that it returns true on success (we aren't changing system's design).
Additionally in this case, mkdir -p is meant to be quiet and it may not say anything when it cannot do its thing, in some cases. This is by design of course, but one should be aware of it.
† A program may return whatever it wants; nothing requires of it to return zero if it succeeded (completed without error) and it does not have to follow any conventions; what it returns is entirely up to its design.
Typical Unix programs generally follow the convention of returning non-zero only if there were problems, but some do return non-zero values merely to indicate a particular condition. See for example here and here.
One example: for at least some versions, zip returns non-zero (12) when it "has nothing to do" (nothing to update in a package for example), even though it completes successfully; so we get 3072 from system in that case (when that 12 is packed into high bits of a number which is the return code). This is documented on its man page.
A zero return, though, normally does imply a successful operation, what is the main point here.
Simplest
system ("mkdir -p Purged") && die "Failed to mkdir.";
The system command passes through the exit code of the program that it ran. It doesn't judge or interpret it. Raku, on the other hand, does interpret it and you have to fight the language to get around it. And, remember that Perl's system is basically C's system (and many things in Perl are just what C's version does).
You need to compare this to the value you expected based on your knowledge of that particular command's behavior, as shown in the system docs:
#args = ("command", "arg1", "arg2");
system(#args) == 0
or die "system #args failed: $?"
Usually, well made unix programs use 0 to indicate success and non-zero for everything else. That's a good rule of thumb, but it's a convention rather than a requirement. Some programs return non-zero values to indicate other sorts of success. system knows none of that. Not only that, many programmers have no idea what an exit code should be, and I've often had to deal with someone's reinvented idea of what that should be (and I've probably done that myself at some point).
There's been a growing trend of "trust and don't verify", which I think is the new generation of programmers not having systems and shell programming backgrounds. And, even then, there's how the world should be and how it actually is. Remember, your goal as the programmer is to know what happened and why, even if the world is messy.
Here are a few interesting links:
Well Behaved Non-Zero Exit Codes?
Non-zero exit status for clean exit
Are there any standard exit status codes in Linux?
I like this statement from Frederick the best, though:
More realistically, 0 means success or maybe failure, 1 means general failure or maybe success, 2 means general failure if 1 and 0 are both used for success, but maybe success as well.
Consider grep. Here's a file to search:
$ cat original.txt
This is the original
This is from append.txt
This is from append.txt
This is from append.txt
Search for something that exists:
$ grep original original.txt
This is the original
The exit code is 0 ($? on the shell, but Perl's $? is more complicated). That's what you expect by convention. You found something and there were no errors:
$ echo $?
0
But now search for something that doesn't exist. You don't find something but there were no errors:
$ grep foo original.txt
Now the exit code is 1, because that's what grep does:
$ echo $?
1
This is non-zero but not an error. You called grep correctly and it produced the correct and expected output.
It's not up to system to judge those exit values. It passes through that exit code and lets you interpret them.
Now, we get lazy and assume that 0 means that it worked. We do that because many people are used to seeing it and it works most of the time. But, "works most if the time" is different than "won't bite me in the ass". Compare the result to system to exactly what you will accept as success:
my $expected = 0;
system('...') == $expected or die ...
If the task's definition of success is that grep finds matching lines, that code works fine because the exit code in that case happens to be zero. But, that accidentally works because two different ideas happen to align at that one point.
But, what you accept as success is not the same thing as the exit code. Consider that grep example. So far you know that both 0 and 1 are normal operation. Both of those mean that grep did not encounter an error. That's different than calling grep incorrectly and getting a different exit value:
$ grep -X foo original.txt
grep: original.txt: Undefined error: 0
$ echo $?
2
That 2 is conventionally used to signal that you called the program incorrectly. For example, you are using switches that exist on one implementation of a tool that don't exist on another (there are other things than line and gnu, but less so).
You might end up with something like this:
my %acceptable = map { $_ => 1 } qw(0 1);
my $rc = system(...);
die ... unless exists $acceptable{$rc};
And, the die can pass through that exit code (see it's method for choosing a value):
$ perl -e 'system( "grep -X foo original.txt" ) == 0 or die'
grep: original.txt: Undefined error: 0
Died at -e line 1.
$ echo $?
2
Sure, that's not as pretty as the single statement, but pretty shouldn't trump correct.
But that's not even good enough. My linux version of grep says it will exit with 0 on error in certain conditions:
Normally the exit status is 0 if a line is selected, 1 if no lines were selected, and 2 if an error occurred. However, if the -q or --quiet or --silent is used and a line is selected, the exit status is 0 even if an error occurred.
Going a step further, there's a list of conventional exit codes in sysexits.h that signal very particular conditions. For example, that exit code of 2 gets the symbol EX_USAGE and indicates a problem with the way the command was formed.
When I'm writing system code, I'm hard core on proper exit codes so other programs can know what happened without parsing error output. The die called by the user without a failure in a system will probably return 255. That's not very useful for other things to figure out what went wrong.
A one-line solution:
system("if printf '' > tmp.txt; then exit 1; else exit 0; fi ;") or die("unable to clobber tmp.txt");

Start perl script with Win32::Process::Create

I would like to translate all of the system commands in my script to Win::32::Process::Create commands. CPAN tells me the syntax:
Win32::Process::Create($obj,$appname,$cmdline,$iflags,$cflags,$curdir)
So, I tried to apply it:
Win32::Process::Create( $Win32processObj,
"C:\\Perl64\\bin\\perl.exe",
"'C:\\Users\\script.pl','$arg'",
0,
NORMAL_PRIORITY_CLASS,
"." ) || die "Failed to create process.\n";
When I run this, I don't get an error, but I don't start a new process either...
When I use GetProcessID(), I get a pid, but it doesn't correspond to anything in the tasklist... (I'm assuming the created process ends before I can see it displayed in the tasklist).
According to your comment, Windows says it created the process. Accoding to your question, you even have its process id of the process you claim never got created. I'm gonna go out on a limb and say you are mistaken.
You should now check what code the process ended with. perl -E'say $!=THECODE;' might give you a hint. But chances are it's because you tell Perl to execute a file named 'C:\Users\script.pl' (as opposed to C:\Users\script.pl).

In Perl, how do I determine if there's a standard input present?

I've got a script that grabs standard input:
&process_input
sub process_input {
while(<STDIN>) {
$log_data .= $_;
}
}
When I run the script:
myscript.pl -param1=a -param2=b
I get stuck in this subroutine. Everything runs OK if I do:
echo "" | myscript.pl -param1=a -param2=b
How do I determine if I even have a standard input?
I would have thought that while(<STDIN>) would return false and not run, but I'm guessing it's actually waiting for you to type something in that why it's 'stuck'.
You want to check where your STDIN (STanDard INput) is coming from: another application or a terminal. In your case, it's the second option, causing a read operation to stall the process until the user inputs something. For a solution, see How can I tell if STDIN is connected to a terminal in Perl?.
if (-t STDIN) {
# input attached to terminal and will probably ask user
} else {
# input from other process
}
There's also IO::Interactive that might do better/more reliable checking.
The statement <STDIN> does not return until you press Enter on the console. If you want to get around this, I believe that you can use IO::Handle to wrap STDIN, and call $stdin->blocking(0) to enable non-blocking I/O.
That's normal. Standard usage for Unix tools is to use STDIN if no input file is given as an argument. Try cat, less, grep, etc. It's up to the caller to provide input, if only
tool < /dev/null
I strongly advise against trying to determine if "input" is available as it will introduce problems no matter how you achieve this. Specifically, avoid -t since it's problematic to fake a terminal when needed. Instead, rely on a more conventional interface.
If you want to make it possible to pass no input to your tool, it's weird that you'd be using STDIN in the first place. One would normally use an optional argument.
tool --foo file
tool --foo <( echo "" )
Another option would be to request that the user tells you when there is no input.
tool --batch
In order to help you with the design problems of your interface, it would really help to know what your tool does.
Your program will continue when the user types Ctrl + D, the end-of-file character.

How can I disable Perl warnings for svnnotify?

I'm using svnnotify. It works (sends email and all that) but it always outputs some error messages, such as
Use of uninitialized value in substr at /usr/lib/perl5/site_perl/5.8.8/SVN/Notify.pm line 1313.
substr outside of string at /usr/lib/perl5/site\_perl/5.8.8/SVN/Notify.pm line 1313.
Use of uninitialized value in index at /usr/lib/perl5/site\_perl/5.8.8/SVN/Notify.pm line 1313.
Use of uninitialized value in concatenation (.) or string at /usr/lib/perl5/site\_perl/5.8.8/SVN/Notify.pm line 1314.
I've tried running it with > /dev/null but no luck. I've tried running it > bla and file bla comes up empty, and the output is shown on screen. Since svnnotify does not have a quiet switch, how can I do this?
It looks like this happens when there is no log message for the commit. Some users may need to have a LART session to fix that. Barring that, the right thing to do is fix the SVN::Notify module and submit the patch to the SVN::Notify RT queue, but too late, I've already submitted the ticket.
Here's the patch:
diff --git a/lib/SVN/Notify.pm b/lib/SVN/Notify.pm
index 3f3672b..5387dd2 100644
--- a/lib/SVN/Notify.pm
+++ b/lib/SVN/Notify.pm
## -1308,7 +1308,7 ## sub prepare_subject {
}
# Add the first sentence/line from the log message.
- unless ($self->{no\_first\_line}) {
+ if (!$self->{no\_first\_line} and defined $self->{message}[0] and length $self->{message}[0] )
# Truncate to first period after a minimum of 10 characters.
my $i = index substr($self->{message}[0], 10), '. ';
$self->{subject} .= $i > 0
I think that takes care of the warnings, but I suspect it's only a band-aid because the design around $self->{no\_first\_line} seems dodgy.
While the accepted answer certainly works, it isn't (in my opinion) the best answer, because one day you're going to have to maintain this code (or worse, someone else will), and getting it to run with no warnings will make your code that much better. Given the warnings, it looks like the problem isn't you, but it's hard (but not impossible) to imagine that a module in version 2.79 would give errors. Maybe your copy of SVN::Notify is old? Maybe your code (or someone's code) is mucking with the module's internals? It's hard to tell with the few warnings I can see, and I have a feeling there's a bit more to this problem than meets the eye.
The error is printed on stderr, you can redirect it to /dev/null with 2> /dev/null. Alternatively you can make it not use perl -w.