Your program has a few red flags. I discuss them below and at the end suggest a better way to write your code.
Red Flag 1
The code from your question attempts to execute external commands with backticks and assumes success. You should always check the status of any call to the operating system. A failed `$command`
—also known as qx//
or readpipe
—makes itself known in one or more ways depending on whether the command executed and failed or whether attempted execution of the command failed:
- The value of the special variable
$?
is non-zero.
- In cases of failed execution
- the special variable
$!
contains a description of the failure.
- the operator returns the undefined value in scalar context or the empty list in list context.
With Perl, `$command`
executes a subshell in order to execute $command
, so the subshell may be the program that executes and fails, e.g., for bad command syntax.
Exactly one command in the example below succeeds on my machine.
#! /usr/bin/env perl
use strict;
use warnings;
my @commands = (
"bad syntax (",
"does-not-exist",
"/etc/passwd",
"perl --no-such-option",
"perl -le 'print q(Hello world)'",
);
foreach my $c (@commands) {
print "Capturing output of command $c...\n";
my $output = `$c`;
if ($? == 0) {
print " - command executed successfully!\n";
}
elsif ($? == -1) {
print " - command failed to execute: \$!=$!\n";
}
else {
print " - command exited with status " . ($? >> 8) . "\n";
}
print " - value of \$output is ",
(defined $output ? "" : "un"), "defined\n\n";
}
Output:
Capturing output of command bad syntax (...
sh: Syntax error: "(" unexpected
- command exited with status 2
- value of $output is defined
Capturing output of command does-not-exist...
Can't exec "does-not-exist": No such file or directory at ./runcmds line 16.
- command failed to execute: $!=No such file or directory
- value of $output is undefined
Capturing output of command /etc/passwd...
Can't exec "/etc/passwd": Permission denied at ./runcmds line 16.
- command failed to execute: $!=Permission denied
- value of $output is undefined
Capturing output of command perl --no-such-option...
Unrecognized switch: --no-such-option (-h will show valid options).
- command exited with status 29
- value of $output is defined
Capturing output of command perl -le 'print q(Hello world)'...
- command executed successfully!
- value of $output is defined
Red Flag 2
Note for example the line of output
Can't exec "does-not-exist": No such file or directory at ./runcmds line 16.
I didn't write the code that generated this warning. Instead, it happened automatically because I enabled the warnings pragma with the line use warnings
. Had you enabled warnings, Perl would have already given you at least a hint about the cause of your problem, so I assume you didn't enable warnings.
This is another red flag. Warnings are there to help you. Always enable them for any non-trivial program.
Red Flag 3
Finally, you're using regex capture-variable $1
unconditionally, and this habit will lead to surprising bugs when the match fails and you get a captured value from different match. You should always wrap uses of $1
, $2
, and friends inside a conditional, e.g.,
if (/pa(tte)rn/) {
do_something_with $1;
}
Recommended Fixes
Near the top of your code just after the shebang line, you should immediately add the line
use warnings;
I'd also recommend
use strict;
but that will likely require more work to clean up your code. We're here to help you understand and overcome any issues you find in that worthwhile effort.
Use output_of
below to capture a command's output or die with an appropriate diagnostic.
sub output_of {
my($cmd) = @_;
my $output = `$cmd`;
return $output if $? == 0;
if ($? == -1) {
die "$0: $cmd failed to execute: $!\n";
}
elsif ($? & 127) {
my $signal = $? & 127;
my $core = ($? & 128) ? ", core dumped" : "";
die "$0: $cmd died with signal $signal$core\n";
}
else {
die "$0: $cmd exited with status " . ($? >> 8) . "\n";
}
}
The section of code from your question becomes
my $output;
$output = output_of 'ec2-run-instances ami-8e1fece7 -k mykey -t t1.micro';
if ($output =~ /INSTANCE\s+(i-\w+)\s/) {
my $instance_id = $1;
$output = output_of "ec2-describe-instances $instance_id";
if ($output =~ /(ec2-(\d+-\d+-\d+-\d+).\S+)/) {
print "$0: found instance $2\n"; # or whatever
}
else {
die "$0: no ec2-IP in ec2-describe-instances output:\n$output";
}
}
else {
die "$0: no INSTANCE in ec2-run-instances output:\n$output";
}
With these changes, your code will supply on the standard error diagnostics for all failure modes in processing output from ec2-run-instances
and ec2-describe-instances
instead of leaving you to wonder what went wrong.