Want to echo good error message in case of linux command failed - bash

I am trying to write a function in bash so that when any command fails, It will print a nice error message, I tried this
#!/bin/bash
msg()
{
if echo $? > 0 ; then
echo "==> Something wrong happened. Please see /var/log/install.log"
else
echo "==> Done"
fi
}
But when I tried to use them in this way:
#!/bin/bash
msg()
{
if echo $? > 0 ; then
echo "==> Something wrong happened. Please see /var/log/install.log"
else
echo "==> Done"
fi
}
mkdir vcvcxvcxv
msg
Then I ran this on terminal
$ bash check
mkdir: vcvcxvcxv: File exists
==> Something wrong happened, Please see /var/log/install.log
I got same output whatever. But I suppose to get the "Done" message when my command succeeds. Is there any help in this regard?

This:
if echo $? > 0
does not do anything you want it to.
if [ $? -ne 0 ]

Although you got your immediate problem fixed already, your code isn't very idiomatic, and violates some fundamental shell scripting principles.
Errors should produce an error status
Error messages should go to standard error
Don't be needlessly verbose
So the simplest fix is really
mkdir vcvcxvcxv || exit
since mkdir already produces an error message on stderr, and exit will propagate the value of $? to the calling process.
Incidentally, you can get much the same effect with set -e but it is slightly cumbersome to use for the newcomer.
If you want the "helpful" error message, perhaps this;
death () {
rc=$?
echo "$0: see /var/log/install.log" >&2
exit $rc
}
mkdir vcvcxvcxv || death
echo "$0: done." >&2
would be closer to what you really need. Notice how the status messages are redirected to standard error (and each has the name of the script, so you can see what's causing the failure when you have scripts calling scripts calling scripts, etc) and the exit status is naturally propagated to the caller, and the entire script aborted, in the case of a fatal error. Also, the flow of the main script becomes more natural as it grows;
something || death
additional thing || death
final thing || death
echo "$0: done." >&2
(Of course, if failure means death always, you actually want set -e.)

Related

sh -e: collecting a command's exit status from the "else" branch of an if

We are writing shell scripts with set -e as policy which means that it will exit if any unhandled non-zero exit status appears.
#!/bin/sh -e
if some_command; then
experience_happyness
else
print error status of some_command to log or standard error
experience_sadness
exit 1
fi
The $? expression evaluates to 0 at the beginning of the else branch. If I don't run the some_command inside if then an eventual error will terminate the shell script immediately.
How can I know the exit status of a program when set -e is effective without terminating the script?
I'm interested in bash specific solutions too if pure sh solutions are not available.
EDIT: My bad. as #"that other guy" answered I was mistaken when I told that "$?" evaluates to 0 at the beginning of the else branch. I tried it, but I made some mistake when tried it. Sorry.
I think we may keep this question because of the pro quality answer. Should we?
The $? expression evaluates to 0 at the beginning of the else branch.
No it doesn't.
#!/bin/sh -e
some_command() { return 42; }
if some_command; then
echo "Worked"
else
echo "Command failed with $?"
exit 1
fi
will print Command failed with 42.

Continue after failed command in bash script with `set -e`

I'm writing a script that parses Bundler's output for errors, but I've run into an issue that I'm not certain how to handle:
response="$(bundle install 2>&1)"
echo $response
If Bundler fails, then $response is blank, and I can't parse it as I need. I've tried:
response="$(bundle install 2>&1 || true)"
echo $response
Which works, but isn't what I want because I still want my script to fail when Bundler fails.
How can I allow bundle install to fail and still read its output?
I should also add that I'm using set -e, which I need.
Edit:
After reading PSkocik's answer, this allows me to continue through the error, while still exiting when it happens:
response="$(bundle install 2>&1)" || bundle_status=$?
# Continue working (In my case, I'm parsing the errors to give more
# meaningful output for the context in which bundler is being run).
# And now I can exit with the given status code, or default to 0.
exit ${bundle_status:-0}
It's because of the -e option.
You need to make sure the error return status gets tested for:
man sh on -e (man bash is a little more verbose on this, but in the same spirit)
If not interactive, exit immediately if any untested command fails.
The exit status of a command is considered to be explicitly tested if
the command is used to control an if, elif, while, or until; or if the
command is the left hand operand of an “&&” or “||” operator.
Here's an example with a bundler mock (in sh, but should work in bash too):
#!/bin/sh
set -e
bundle(){
echo "i'm errorring here" >&2
return 32
}
response="$(bundle 2>&1)"
echo "won't get here if -e is set"
You can solve it like this:
response="$(bundle 2>&1)" || ret=$? #`set -e` won't kill us now
echo "will get here now"
echo "the bundle command exited with $ret"
echo "the response is ${response}"
If you need to echo strings that aren't yours, you should consider printf instead of echo (and don't forget double quotes): https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo
One option is to get rid of -e and find a way to do more sophisticated error handling.
The other option is to replace the bundle command with a wrapper. Example:
function my-bundle {
set +e
output=$(bundle "$#" 2>&1)
ret=$?
echo "$output" # Add code for output parsing here
set -e
return $ret
}
# main script
set -e
There is no way you can have set -e handle the return status of a command and run code after the command. (Unless you want to use trap)
The problem is, you don't want your script to fail when Bundler does. You just want to exit after you've caught the error and done something with the error message. Don't use set -e.
if ! response=$(bundle install 2>&1); then
ret=$?
# do something with $reponse
exit $ret
fi

Catching all bad signals for called commands in Bash script

We are creating a bash script for a build server. We want to ensure that when we execute a bash command inside the script, it returns with a signal of 0. If it does not, we want execution to stop. Our solution so far is to do:
#some command
if [ $? -ne 0 ] ; then
#handle error
fi
after every command that could cause this problem. This makes the code quite long and doesn't seem very elegant. We could use a bash function, perhaps. Although working with $? can be a bit tricky, and we would still have to call the function after every command. Is there a better way? I've looked at the trap command but it seems to only do signal handling with the bash script I am writing, not any commands I call.
The robust, canonical way of doing this is:
#!/bin/bash
die() {
local ret=$?
echo >&2 "$*"
exit "$ret"
}
./configure || die "Project can't be configured"
make || die "Project doesn't build"
make install || die "Installation failed"
The fragile, convenient way of doing this is set -e:
#!/bin/bash
set -e # Script will exit in many (but not all) cases if a command fails
./configure
make
make install
or equivalently (with a custom error message):
#!/bin/bash
# Will be called for many (but not all) commands that fail
trap 'ret=$?; echo >&2 "Failure on line $LINENO, exiting."; exit $ret' ERR
./configure
make
make install
For the latter two, the script will not exit for any command that is part of a conditional statement or &&/||, so while:
backup() {
set -e
scp backup.tar.gz user#host:/backup
rm backup.tar.gz
}
backup
will correctly avoid executing rm if the transfer fails, later inserting a feature like this:
if backup
then
mail -s "Backup completed successfully" user#example.com
fi
will make the backup stop exiting on failure and accidentally delete backups.

Is it possible to check the output of previous step in the same script

The script runs two scripts depending on the output of the first script, i want to choose to run the script or not. For e.g
Mainscript.sh
./script1.sh....
is there a way to check output of script1.sh in mainscript.sh?
Because if output that there is connection between two system then i need to run the second script. Can i do it with python or i can do with bash only?
A common arrangement is to write your scripts so that programmatic operation is convenient. In concrete terms, that means that your script should return a zero exit code on success, nonzero otherwise. Then what you ask for is a simple matter of
script1 && script2
or in more complex cases
if script1; then
echo "$0: countdown to takeoff" >&2
script2
else
echo "$0: trouble occurred" >&2
script1 --reverse --undo --apologize
script3 || make clean
echo "$0: reverted everything" >&2
fi
You can access the last command exit code in $?. Also you can save its output.
#!/bin/sh
OUTPUT=`./script1.sh`
echo $?
echo $OUTPUT

Calling script from script

How can I have my shell script echo to me that the script that it calls has failed?
#!/bin/sh
test="/Applications/test.sh"
sh $test
exit 0
exit 1
#!/bin/sh
if sh /Applications/test.sh; then
echo "Well done $USER"
exit 0
else
echo "script failed with code [$?]" >&2
exit 1
fi
The /Applications/test.sh script should be well coded to exit with conventional status. 0 if it's ok and > 0 if it fails.
Like you can see, no need to test the special variable $?, we use boolean expression directly.
I usually take the following approach:
#!/usr/bin/env bash
test="/Applications/test.sh"
sh "${test}"
exit_status=$?
if [[ ${exit_status} ]] ; then
echo "Error: ${test} failed with status ${exit_status}." >&2
else
echo "Success!"
fi
In terms of best practice, you should not. If a script fails, it should emit an error message before it terminates so that its parent doesn't have to. The main reason for this is that the process knows why it is failing, while the parent can only guess. In other words, you should just write:
#!/bin/sh
test="/Applications/test.sh"
sh $test
Although really, it would be more typical to just write:
#!/bin/sh
/Applications/test.sh
test.sh will emit the necessary error message, and your script will return the same value as did test.sh. Also, in its current form your script will always be successful, even if test.sh actually failed because exit 0; exit 1 is pretty pointless: the exit 1 will never be called.

Resources