I have a large iptables ruleset that I manage with my own bash script. Most of the commands in the script are simple, single-statment iptables commands. I am trying to improve the script by adding success/failure output as the script executes.
I have the script broken out into different sections. One example would be the FORWARD chain section, where all the rules are applied to the FORWARD chain. At the beginning of the section, I output that the script has started applying the FORWARD rules, and at the end, I want to output whether or not all the rules were applied successfully, or if any of them didn't work. Here is the basic idea:
#Start FORWARD section
echo -ne "Applying FORWARD rules..."
#rule 1
/sbin/iptables -A FOWRARD...
#rule 2
/sbin/iptables -A FORWARD...
echo -ne "\t\t\t[OK]\n"What I'm wanting to do is catch any output or errors that may result from each iptables command and store them in an array or something. Then at the end of the block, use an if statement to evaluate the array to see if there were any errors. If not, output the [OK] status, if there were, output the [FAILED] status and display the related error.
Is there a way I can do this for the entire block of rules without wrapping each iptables rule in an if [ $? != 0 ] expression?
33 Answers
You could use the trap shell builtin command to have a handler function called if a command has a non-zero exit status. You can pass necessary information, like the line number and the exit status to your error handler function.
Example:
#!/bin/bash
handle_error() { echo "FAILED: line $1, exit code $2" exit 1
}
trap 'handle_error $LINENO $?' ERR
# your commands here
# ...
echo "OK" 2 The below script will define the recordfailure function which should be prepended to commands as shown in the example after the function definition. If the command returns non-zero, the standard error stream is logged. Finally, on the end, the error log is checked and [OK] or [FAIL] is printed accordingly.
#!/bin/bash
# Redirect file descriptor #3 to standard output (used in recordfailure)
exec 3>&1
# create an array for holding failures
declare -a failures
# recordfailure command arg1 arg2 ... argN
recordfailure() { local error retval # Run the command and store error messages (output to the standard error # stream in $error, but send regular output to file descriptor 3 which # redirects to standard output error="$("$@" 2>&1 >&3)" retval=$? # if the command failed (returned a non-zero exit code) if [ $retval -gt 0 ]; then if [ -z "$error" ]; then # create an error message if there was none error="Command failed with exit code $retval" fi # uncomment if you want the command in the error message #error="Command $* failed: $error" # append the error to $failures, ${#failures[@]} is the length of # the array and since array start at index 0, a new item is created failures[${#failures[@]}]="$error" # uncomment if you want to show the error immediately #echo "$error" fi
}
recordfailure iptables -A FORWARD ...
recordfailure iptables -A FORWARD ...
# if the length of the failures array equals 0 (no items) everything is OK
if [ ${#failures[@]} -eq 0 ]; then echo "[OK]"
else echo "[FAIL]" # list every error for failure in "${failures[@]}"; do # optionally color it, format it or whatever you want to do with it echo "error: $failure" done
fiIf you've no desire for showing standard output, remove exec 3>&1 and replace >&3 by >/dev/null.
You mean something like this?
ok=1
#Start FORWARD section
echo -ne "Applying FORWARD rules..."
#rule 1
/sbin/iptables -A FOWRARD... || ok=0
#rule 2
/sbin/iptables -A FORWARD... || ok=0
echo -ne "\t\t\t"
if [ $ok -eq 1 ]; then echo "[OK]"
else echo "[fail]"
fi