Conditional actions based on error in shell script - bash

I have a bash script that watch's a directory for uploads. When it sees a xml file in the directory. The script takes the file and calls a java command line program that using xslt transforms the file. I want to mv the file to a different directory (errors) if the script or java command line program throws a error during processing. Then email me the error.
I was going to put
exec 2> mail -s 'Statement Error Processing'
at the top of the script to catch output for stderr. But that doesn't seem very elegant and doesn't move the file in question.

You can trap error conditions in bash (any statement that returns a non-zero condition that isn't handled inline):
trap func ERR
Then in func (a function you define) you can do whatever you need to clean up.
For example:
#!/bin/bash
function foo() {
echo "cleaning up"
}
trap foo ERR
echo "about to execute false"
false
echo "exiting"
Running that will result in:
./foo.sh
about to execute false
cleaning up
exiting

You need two things here. First, determine if the program fails. This is usually known by the return value of the program's main() function. If the program returns anything different to 0, there is an error.
In any case, the second thing you have to do is to capture the standard error output to a file to later mail it. So:
if ! program inputfile 2> errorfile ; then
mv inputfile error_directory
mail -s "error" < errorfile
fi
rm errorfile
You have to check, though, if your program follows this convention to signal an error.

Related

How to make exception for a bash script with set -ex

I have a bash script that has set -ex, which means the running script will exit once any command in it hits an error.
My use case is that there's a subcommand in this script for which I want to catch its error, instead of making the running script shutdown.
E.g., here's myscript.sh
#!/bin/bash
set -ex
# sudo code here
error = $( some command )
if [[ -n $error ]] ; then
#do something
fi
Any idea how I can achieve this?
You can override the output of a single command
this_will_fail || true
Or for an entire block of code
set +e
this_will_fail
set -e
Beware, however, that if you decide you don't want to use set -e in the script anymore this won't work.
If you want to handle a particular command's error status yourself, you can use as the condition in an if statement:
if ! some command; then
echo 'An error occurred!' >&2
# handle error here
fi
Since the command is part of a condition, it won't exit on error. Note that other than the ! (which negates it, so the then clause will run if the command fails rather than it succeeds), you just include the command directly in the if statement (no brackets, parentheses, etc).
BTW, in your pseudocode example, you seem to be treating it as an error if the command produces any output; usually that's not what you want, and I'm assuming you actually want to test the exit status to detect errors.

Bash: Get message from error that was just trapped

Bash allows you to trap signals. Is there a way to actually get the message that printed immediately before (the cause of) a particular signal? It's ERR in particular I'm interested in. I'm aware that not all signals are associated with a message. Just wondering if bash itself sets a variable or something when it raises an error.
Sample code:
#!/bin/bash
# Running bash 5.0
handler () {
echo "Handling the error"
exit 0
}
trap handler ERR
notacommand
The code above will print:
./example.sh: line 11: notacommand: command not found
Is there a way to see this message inside the handler?
Edit: I know I could save all output to a file and then read the tail of that file when an error occurs. That seems problematic to me, as it's possible that the last message written to the file is something other than the error (especially if any subprocesses are started with & in the script). I was hoping that maybe bash sets a var or something, in the same way it sets $1, $?, $RANDOM, and others.
Redirect bash's stderr to a file:
#!/bin/bash
# Running bash 5.0
log="error.txt"
exec 2>"$log"
handler () {
echo "Handling this error: $(< "$log")"
exit 0
}
trap "handler" ERR
trap "rm $log" EXIT
notacommand
Output:
Handling this error: ./example.sh: line 15: notacommand: command not found

Is Exception handling possible in Unix shell script, which includes calling another scripts internally

I have a Scenario where in I need to fetch some data by triggering another external bash file from my shell script. If I end up with any error output from external bash, My shell script should handle and should go through the fall back approach. But I am actually facing issue with that external bash file, Wherein bash returns (exit 1) in failure cases, which causes my script also to exit and never executing fall back approach. Can anyone guide how to handle the exit from external bash and run my fall back approach.
Not sure if this works in sh, but it works in bash.
I made a try / except tool out of this, but it will work here too I believe.
#! /bin/bash
try() {
exec 2> /dev/null
#direct stderr out to /dev/null
#main block
input_function="$1"
#fallback code
catch_function="$3"
#open a sub shell
(
#tell it to exit upon encountering an error
set -e
#main block
"$#"
)
#if exit code of above is > 0, then run fallback code
if [ "$?" != 0 ]; then
$catch_function
else
#success, it ran with no errors
test
fi
#put stderr back into stdout
exec 2> /dev/tty
}
An example of using this would be:
try [function 1] except [function 2]
Function 1 would be main block of code, and 2 would be fallback function/block of code.
Your first function could be:
run() {
/path/to/external/script
}
And your second can be whatever you want to fall back on.
Hope this helps.

Bash script does not quit on first "exit" call when calling the problematic function using $(func)

Sorry I cannot give a clear title for what's happening but here is the simplified problem code.
#!/bin/bash
# get the absolute path of .conf directory
get_conf_dir() {
local path=$(some_command) || { echo "please install some_command first."; exit 100; }
echo "$path"
}
# process the configuration
read_conf() {
local conf_path="$(get_conf_dir)/foo.conf"
[ -r "$conf_path" ] || { echo "conf file not found"; exit 200; }
# more code ...
}
read_conf
So basically here what I am trying to do is, reading a simple configuration file in bash script, and I have some trouble in error handling.
The some_command is a command which comes from a 3rd party library (i.e. greadlink from coreutils), required for obtain the path.
When running the code above, I expect it outputs "command not found" because that's where the FIRST error occurs, but actually it always prints "conf file not found".
I am very confused about such behavior, and I think BASH probably intent to handle thing like this but I don't know why. And most importantly, how to fix it?
Any idea would be greatly appreciated.
Do you see your please install some_command first message anywhere? Is it in $conf_path from the local conf_path="$(get_conf_dir)/foo.conf" line? Do you have a $conf_path value of please install some_command first/foo.conf? Which then fails the -r test?
No, you don't. (But feel free to echo the value of $conf_path in that exit 200 block to confirm this fact.) (Also Error messages should, in general, get sent to standard error and not standard output anyway. So they should be echo "..." 2>&1. That way they don't be caught by the normal command substitution at all.)
The reason you don't is because that exit 100 block is never happening.
You can see this with set -x at the top of your script also. Go try it.
See what I mean?
The reason it isn't happening is that the failure return of some_command is being swallowed by the local path=$(some_command) assignment statement.
Try running this command:
f() { local a=$(false); echo "Returned: $?"; }; f
Do you expect to see Returned: 1? You might but you won't see that.
What you will see is Returned: 0.
Now try either of these versions:
f() { a=$(false); echo "Returned: $?"; }; f
f() { local a; a=$(false); echo "Returned: $?"; }; f
Get the output you expected in the first place?
Right. local and export and declare and typeset are statements on their own. They have their own return values. They ignore (and replace) the return value of the commands that execute in their contexts.
The solution to your problem is to split the local path and path=$(some_command) statements.
http://www.shellcheck.net/ catches this (and many other common errors). You should make it your friend.
In addition to the above (if you've managed to follow along this far) even with the changes mentioned so far your exit 100 won't exit the main script since it will only exit the sub-shell spawned by the command substitution in the assignment.
If you want that exit 100 to exit your script then you either need to notice and re-exit with it (check for get_conf_dir failure after the conf_path assignment and exit with the previous exit code) or drop the get_conf_dir function itself and just do that inline in read_conf.

complex command within a variable

I am writing a script that among other things runs a shell command several times. This command doesn't handle exit codes very well and I need to know if the process ended successfully or not.
So what I was thinking is to analyze the stderr to find out the word error (using grep). I know this is not the best thing to do, I'm working on it....
Anyway, the only way I can imagine is to put the stderr of that program in a variable and then use grep to well, "grep" it and throw it to another variable. Then I can see if that variable is valorized, meaning that there was an error, and do my work.
The qustion is: how can I do this ?
I don't really want to run the program inside a variable, because it has got a lot of arguments (with special characters such as backslash, quotes, doublequotes...) and it's a memory and I/O intensive program.
Awaiting your reply, thanks.
Redirect the stderr of that command to a temporary file and check if the word "error" is present in that file.
mycommand 2> /tmp/temp.txt
grep error /tmp/temp.txt
Thanks #Jdamian, this was my answer too, in the end.
I asked my principal if I can write a temp file and it allowed, so this is the end result:
... script
command to be launched -argument -other "argument" -other "other" argument 2>&1 | tee $TEMPFILE
ERRORCODE=( `grep -i error "$TEMPFILE" `)
if [ -z $ERRORCODE ] ;
then
some actions ....
I didn't tested this yet because I got some other scripts involved that I need to write before.
What I'm trying to do is:
run the command, having its stderr redirected to stdout;
using tee, have the above result printed on screen and also to the temp file;
have grep to store the string error found on the temp file (if any) in a variable called ERRORCODE;
if that variable is populated (which mean if it has been created by grep), then the script stops, quitting with status 1, else it contiunes.
What do you think ?
If you don't need the standard output:
if mycommand 2>&1 >/dev/null | grep -q error; then
echo an error occurred
fi

Resources