i am writing a shell script which should exit in some cases, my current code is:
set -e
Running A || (echo "failed to run A"; exit 1)
Running B || (echo "failed to run B"; exit 1)
if Condition C
then
Running D || (echo "failed to run D"; exit 1)
....
and it worked as expected, it passes if A/B runs well, echo message for me if A or B failed, but i have not tested D failed yet. My understanding is if A or B failed, it will go to code after "||", echo error, then exit subshell and return false. So the whole line is "false||false" so it exited because i have "set -e" at the beginning. But then i read How to exit if a command failed?
it mentioned "set -e" is dangerous because it won't be triggered inside if statement, so does it mean that it won't work if my "Running D" command fails?(meanning it won't exit and will continue executing next command).
Also, i tried to use {} as the above post suggests, but it's weird. if i change it to:
#set -e
Running A || {exit 1}
it's correct, meaning if A successes, continue next command, if A fails, exit script. But if i change to
#set -e
Running A || {echo "running A failed";exit 1}
it always exited here no matter A successes or fails.
What is wrong? Also i am a bit confused now which is the best way to exit in my case? i should use "set -e" or "{}"? Thanks!
Using ( ) spawns a sub-shell, while using { } does not, the latter allowing the enclosed exit to directly terminate your script.
The group command syntax using { } is a little picky about syntax. You have to separate the brace characters from others with whitespace. You also need to use a trailing semicolon after the last command in the group.
Given the above constraints, try changing:
Running A || {echo "running A failed";exit 1}
... to:
Running A || { echo "running A failed";exit 1; }
Related
I would like to use a small script to do some cosmetic work to the output of my gcc.
So I use this command:
mygcc foo.c 2>&1 | myscript.pl
Basically my script does things like this:
$error = 0;
while(<>)
{
s/^"(.*)"\s*,\s*line\s*(\d+)\s*:\s*(cc\d+)\s*:/colored("[$3]", 'bold red').colored(" $1", 'red').":".colored("$2", 'yellow')/ge;
s/ \^/colored(" ^", 'yellow')/e;
s/(error:.*$)/colored($1, 'red')/ge;
s/(warning.*$)/colored($1, 'yellow')/ge;
print;
$error = -1;
}
Unfortunately the exit code from gcc is not correctly propagated through the pipe. What I need to do is to get the exit code from gcc and write it back from my script.
Without this, make won't correctly stop the build process in case of an error.
How can I achieve this?
Try using a sub shell:
( mygcc foo.c; echo "gcc returned $?" ) |& myscript.pl
The ( cmd ) construct is used to launch cmd in a sub-shell. Your current shell will fork itself, and the commands will be executed by the child shell. It's an easy way to run multiple commands and have the output fed to a pipe.
The $? variable is the exit status of the last command.
The cmd1 |& cmd1 construct is equivalent to cmd1 2>&1 | cmd2
Take a look at this. You can then use the %ENV variable to access the gcc return status and return that value from your perl script.
#!/bin/sh
date;
perl aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.pl
date;
The script does not exist and it should fail.
Exit status (0 is success, everything else is failure) of script is exit status of last command. To fix, store the right exit status in shell variable before doing something else. Example:
#!/bin/sh
date
perl aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.pl
perlexitstatus=$?
date
exit $perlexitstatus
For completeness, two other ways:
#!/bin/sh
set -e # exit on non-zero command exitcode
date
perl aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.pl
date
Above is often a bit inconvenient, because often shell script has commands you don't care about, and don't want the script to fail for them.
#!/bin/sh
set -e # exit on non-zero command exitcode
date
perl aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.pl ||
{ echo "aaaa... Script failed, spam monkey#company.com"; exit 1;}
date
Last one could be written with if-then-fi statement too, if preferred. It provides a way to print custom error message, in case the failing command's message is unclear.
If you set shebang with a shell you could provide -e parameter to make script break on inner command failures:
#!/bin/sh -e
date;
perl aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.pl
date;
I have a perl script which returns value "return 1;" or "return 0;".I tried also "exit 1;" and "exit 0;"
In my makefile I have the following code:
ifneq ($(TRG_TYPE),node)
VAR := $(shell perl $(ROOT)/Make/chipdep.pl $(ROOT) $(CHIP) $(NAME) )
ifeq ($(VAR),1)
$(error just a test error. Aborting)
endif
endif
I know that perl script works - I tested it separately, but I never see this error and abort, even when it should be seen!
The value of a shell invocation is the output of the shell command, not its exit code. Make your script print 0 or print 1, or in the general case, do $(shell yourcmd >/dev/null 2>&1; echo $$?) to obtain just the exit code from a shell command.
This seems slightly backwards, though. If your script returns 0 for success and 1 for failure, maybe you should simply run it as the first command of your target; the target will abort if any of its commands fail.
I have a perl script for setting up a remote host. And this is its interrupt handler in case if something will go wrong:
sub interrupt
{
$SIG{'__DIE__'} = '';
my $signal = shift;
print STDERR "Error $SELF_NAME: Bootstrapping of host '$REMOTE_HOST' is interrupted with error '$signal', deleting remote temporary directory $REMOTE_TEMP_DIR.\n";
remote_exec("perl -e \"use File::Path; rmtree('$REMOTE_TEMP_DIR');\"", $REMOTE_HOST, $REMOTE_EXEC_METHOD, $REMOTE_EXEC_PORT, $USERNAME, $PASSWORD, 0, 1);
exit 1;
}
And this handler is always called when there is a need. So I can see the error about interrupted bootstrapping in STDERR. But exit 1; is not called and script returns with exit_code = 0. BUT if I add this line print STDERR "After remote_exec and before exit"; between last two lines of my handler it works fine (i.e. returns with exit_code = 1).
Note: remote_exec just calls system($COMMAND) inside as I'm testing it on a local host.
UPDATE
Adding some details about how the script is being called:
I run the script from my C++ program which tracks its standard logs and checks exit status and in case when exit status is not equal to 0 it prints some error. So, when I add some extra line in my script between exit and system I can see the error my C++ program prints, but if there is not such extra line the C++ program tells that the script is successfully exited, which means that exit status is 0.
You didn't actually demonstrate the problem, so I had to guess at how to demonstrate it, and failed.
$ perl -e'
sub interrupt {
$SIG{"__DIE__"} = "";
my $signal = shift;
print STDERR "...\n";
system("echo ...");
exit 4;
}
$SIG{INT} = \&interrupt;
<>;
'
^C...
...
$ echo $?
4
(Used 4 cause it's more distinctive than 1.)
What do you even mean by "not called"? You seem to indicate the program did exit as a result of the interrupt, which means it got called.
So, I have a bash script inside of which I'd like to have a conditional which depends on what a perl script returns. The idea behind my code is as follows:
for i in $(ls); do
if $(perl -e "if (\$i =~ /^.*(bleh|blah|bluh)/) {print 'true';}"); then
echo $i;
fi;
done
Currently, this always returns true, and when I tried it with [[]] around the if statement, I got errors. Any ideas anyone?
P.s. I know I can do this with grep, but it's just an example. I'd like to know how to have Bash use Perl output in general
P.p.s I know I can do this in two lines, setting the perl output to a variable and then testing for that variables value, but I'd rather avoid using that extra variable if possible. Seems wasteful.
If you use exit, you can just use an if directly. E.g.
if perl -e "exit 0 if (successful); exit 1"; then
echo $i;
fi;
0 is success, non-zero is failure, and 0 is the default if you don't call exit.
To answer your question, you want perl to exit 1 for failure and exit 0 for success. That being said, you're doing this the wrong way. Really. Also, don't parse the output of ls. You'll cause yourself many headaches.
for file in *; do
if [[ $file = *bl[eau]h ]]; then
echo "$file matches"
fi
done
for file in * ; do
perl -e "shift =~ /^.*(bleh|blah|bluh)/ || exit 1" "$file" && echo $file: true
done
You should never parse the output of ls. You will have, at least, problems with file names containing spaces. Plus, why bother when your shell can glob on its own?
Quoting $file when passing to the perl script avoids problems with spaces in file names (and other special characters). Internally I avoided expanding the bash $file variable so as to not run afoul of quoting problems if the file name contained ", ' or \
Perl seems to (for some reason) always return 0 if you don't exit with an explicit value, which seems weird to me. Since this is the case I test for failure inside the script and return nonzero in that case.
The return value of the previous command is stored in the bash variable $?. You can do something like:
perl someargs script.pl more args
if [ $? == 0 ] ; then
echo true
else
echo false
fi
It's a good question, my advice is: keep it simple and go Posix (avoid Bashisms1) where possible..
so ross$ if perl -e 'exit 0'; then echo Good; else echo Bad; fi
Good
so ross$ if perl -e 'exit 1'; then echo Good; else echo Bad; fi
Bad
1. Sure, the OP was tagged bash, but others may want to know the generic-Posix form.