How to keep Makefile running but still maintain the error code? - makefile

This is my current code
.PHONY: setup-kind
setup-kind:
-eksctl get cluster integ-clus
([ $$? -eq "0" ] && echo "cluster exists") || echo "cluster does not exist"
This cluster does not exist so I expect "cluster does not exist" to be printed. The command(eksctl get cluster integ-clus) will fail because the cluster doesn't exist but the Makefile will keep running because i added a '-' to the start of the command(Ignore error)
The issue is that then the status of the command becomes a '0' because of my ignore addition and "cluster exists" will be printed as a result.
Does anyone have any suggestions on how I can approach this and get the code to execute the "cluster exists" block/logic?

This is a common beginner error. Out of the box, every line in a make recipe executes in a separate subshell; the second line has no idea what happened in the first.
In this case, there is absolutely no reason to use two lines, anyway.
.PHONY: setup-kind
setup-kind:
eksctl get cluster integ-clus \
&& echo "cluster exists" \
|| echo "cluster does not exist"
(The backslashes escape the newlines, so this is logically a single line.)
The result will be the exit code from either of the echos, so there is no need for a leading minus any longer.
Perhaps see also Why is testing ”$?” to see if a command succeeded or not, an anti-pattern?
By design, make will abort the current recipe if a command fails, so there is no well-defined way for a recipe to return a nonzero exit code and not abort the make or ignore the error result.

Related

How to detect "Cannot fork" errors in bash?

For currently unknown reasons, one of our bash-scripts produces "Cannot fork" errors when running a simple line like:
myvar=`mycmd || echo "error"; exit 2`
Obviously the problem is that no new process can be created (forked) so that command fails.
However bash just ignores the error and continues in the script which caused unexpected problems.
As you can see, we already check for errors in the command itself, but the "Cannot fork" error appears before the command is even run.
Is there a way to catch that error and stop bash from execution?
There are actually several problems with this error check, that'll prevent it from properly handing any error, not just "Cannot fork" errors.
The first problem is that || has higher precedence than ;, so mycmd || echo "error"; exit 2 will run echo "error" only if mycmd fails, but it'll run exit 2 unconditionally, whether or not mycmd succeeds or fails.
In order to fix this, you should group the error handling commands with { }. Something like: mycmd || { echo "error"; exit 2; }. Note that a ; or line break before } is required, or the } will be treated as an argument to exit.
(BTW, I sometimes see this shorthanded as mycmd || echo "error" && exit 2. Don't do this. If the echo fails for some weird reason, it won't run the exit.)
Also all of this, the echo and the exit, is run in the subshell created by the backticks (or would be, if that subshell had forked successfully). That means the error message will get saved in myvar rather than printed (error messages should generally be sent to standard error, e.g. echo "error" >&2); and more importantly it'll be the subshell that exits, not the shell that's running the script. The main script will note that the subshell exited with an error... and blithely keep running. (Well, unless you have -e set, but that's a whole other ball of potential bugs.)
The solution to that is to put the || stuff outside the backticks (or `$( ), since that's generally preferred over backticks). That way it happens in the main shell, that's what prints the error, that's what exits if there's an error, etc. This should also solve the "Cannot fork" problem, although I haven't tested it.
So, with all these corrections, it should look something like this:
myvar=$(mycmd) || {
echo "error" >&2
exit 2
}
Oh, and as Charles Duffy pointed out in a comment, if you use something like local myvar=$(mycmd) or export myvar=$(mycmd), the local/export/whatever command will override the exit status from mycmd. If you need to do that, set the variable's properties separately from its value:
local myvar
myvar=$(mycmd) || {
...

Unsure how to send error code if command within a shell script fails

I recently learned about an if statement that looks at previous command and if failed will send exit code 1 with a message. I can't remember the entire statement but it starts with the below:
if [ $? != "0" ]
How does this statement end? Does it follow every command within a script?
Don't do that. Explicitly referencing $? is almost never necessary. If you want to exit with status 1 when a command fails, you can simply do:
cmd || exit 1
If you want to exit with the same non-zero value returned by the command, you can simply do:
cmd || exit
There are a lot of examples of bad code out there that instead do things like:
cmd
if [ "$?" -ne 0 ]; then echo cmd failed >&2; exit 1; fi
and this is bad practice for many reasons. There's no point in having the shell print a generic message about failure; the command itself ought to have written a detailed error message already, and the vague "cmd failed" is just line noise. Also, you will often see set -e, which basically slaps a || exit on the end of every simple command but has a lot of unintended side effects and edge cases, and its implementation has changed throughout history and different versions of the same shell will handle the edge cases differently so it's really not a good idea to use it.
As to the question "how does this statement end?"; it ends with fi. The general form of if is if CMD; then ...; else ...; fi where CMD is some set of pipelines (eg, you can do if echo foo | grep bar; cmd2 | foo; then ....). If the CMD returns a 0 status the first set of commands (between "then" and "else") is executed. If CMD returns non-zero, the commands between "else" and "fi" are executed. The "else" clause is optional. Don't be fooled by [; it is simply a command. In my opinion, it would be clearer if you used its alternate spelling test, which does not require a final argument of ]. IOW, you could write if test "$?" -ne 0; then ...; fi.

Bash script failing of no reason

I have this bash script (actually a part of https://github.com/ddollar/heroku-buildpack-multi/blob/master/bin/compile with echo I've added myself):
echo "[DEBUG] chmod done"
framework=$($dir/bin/detect $1)
echo "[DEBUG] $framework done"
And I see in the log:
[DEBUG] chmod done
Staging failed: Buildpack compilation step failed
And I do not see the second echo in the logs at all.
I do not know much bash unfortunately. Could anybody explain to me in what case the first echo performs and second do not? I always thought that both echo should always work no matter whether the second line succeeds or not.
It's not visible in your question, but clicking on your link, it says in the third line
set -e
This means to stop processing the script immediately whenever an error occurs. Comment that line, and the script should run through and also print the second echo statement.
Note that I didn't inspect what the script actually does and I cannot tell you if commenting out set -e is actually good advice or not.
From man set:
−e: When this option is on, when any command fails (for any of the reasons listed
in Section 2.8.1, Consequences of Shell Errors or by returning an exit status
greater than zero), the shell immediately shall exit with the following excep‐
tions:
1. The failure of any individual command in a multi-command pipeline shall not
cause the shell to exit. Only the failure of the pipeline itself shall be
considered.
2. The −e setting shall be ignored when executing the compound list following
the while, until, if, or elif reserved word, a pipeline beginning with the
! reserved word, or any command of an AND-OR list other than the last.
3. If the exit status of a compound command other than a subshell command was
the result of a failure while −e was being ignored, then −e shall not apply
to this command.
This requirement applies to the shell environment and each subshell environment
separately. For example, in:
set -e; (false; echo one) | cat; echo two
the false command causes the subshell to exit without executing echo one; how‐
ever, echo two is executed because the exit status of the pipeline (false; echo
one) | cat is zero.

How can I use an if statement in bash to check if no errors have occurred?

I have a bash script I want to self-destruct on execution. So far it works great but I'd like some final check that if no errors have occurred (any output to stderr), then go ahead and self destruct. Otherwise, I'd like to leave the script in tact. I have the code for everything except the error check. Not sure if I can just output err to a file and check if file is empty. I'm sure it's a simple solution.
How could I do this?
Thanks for any help.
You can try this out. $? contains the return code for the process last executed by command. Moreover standard nix derivatives demarcate 0 as (no error) and 1 - 255 as some kind of errors that happened. Note that this will report errors that do not necessarily have any stderr output.
command
if [ "$?" -ne 0 ]; then
echo "command failed";
# your termination logic here
exit 1;
fi
Assuming that your script returns the value 0 on success, a value from 1 to 255 if an error occur you can use the following command
if /path/to/myscript; then
echo success
else
echo failed
fi
you can also use the following (shorter) command
[[ /path/to/myscript ]] && echo success || echo failed

Check if file exists and continue else exit in Bash

I have a script that is one script in a chain of others that sends an email.
At the start of the script I want to check if a file exists and continue only if it exists, otherwise just quit.
Here is the start of my script:
if [ ! -f /scripts/alert ];
then
echo "File not found!" && exit 0
else
continue
fi
However I keep getting a message saying:
line 10: continue: only meaningful in a `for', `while', or `until' loop
Any pointers?
Change it to this:
{
if [ ! -f /scripts/alert ]; then
echo "File not found!"
exit 0
fi
}
A conditional isn't a loop, and there's no place you need to jump to. Execution simply continues after the conditional anyway.
(I also removed the needless &&. Not that it should happen, but just in case the echo fails there's no reason not to exit.)
Your problem is with the continue line which is normally used to skip to the next iteration of a for or while loop.
Therefore just removing the else part of your script should allow it to work.
Yes. Drop the else continue. It's entirely unneeded.

Resources