I fully agree with Druuna.
Since the exit status value (
$?) itself does not matter -- we are only interested if it was nonzero --, I prefer to use a bit different form of a shell function (
Fatal) for this:
Code:
FatalRecipient='my-address@example.com'
FatalSubject='This thing just failed, see'
function Fatal () {
if [ $? -ne 0 ]; then
echo -e "$@" | mailx -s "$FatalSubject" "$FatalRecipient"
exit 1
fi
return 0
}
The idea is that you append
|| Fatal 'Description' after each command that should not fail. If, and only if, the left side of the
|| fails, the
Description is e-mailed to
$FatalRecipient using subject
$FatalSubject :
Code:
cp -pf oldfile newfile || Fatal "Cannot copy 'oldfile' to 'newfile'."
If you want to fail unconditionally, use
Code:
false || Fatal 'Description'
since
false will do nothing but fail; it does nothing but return a nonzero exit status.
Here is a real world example:
A typical operation in a script like this is to create a temporary working directory. This is how to do it so that it will be automatically deleted when the script exits, no matter why the script exits. If the temporary directory cannot be created, the script is aborted, and an error e-mail sent:
Code:
tempdir="$(mktemp -d)" || Fatal 'Cannot create a temporary directory.'
trap "rm -rf '$tempdir'" EXIT
The
trap command removes the directory whenever the interpreter exits. (Because the command is in double quotes, the value of
$tempdir is evaluated right then and there, when the trap is set. This means that even if you change
tempdir later on, it will not affect the trap; regardless of the value of
$tempdir when the script exits, the trap will always remove the original directory.)
Let us expand this example a bit further.
Let us say you wish to apply a
sed operation to say all files in and under
/var/lib/mystuff/. The following form requires Bash (
read -rd ""), but does support all possible file names, even those containing whitespace or newlines or other weird characters:
Code:
LANG=C
LC_ALL=C
find /var/lib/mystuff/ -type f -print0 | while read -rd "" FILE ; do
cp -f --preserve=all "$FILE" "$tempdir/copy" || Fatal "Could not backup '$FILE'."
sed -e 's|something|other|g' "$FILE" > "$tempdir/copy" || Fatal "Could not edit '$FILE' using sed."
mv -f "$tempdir/copy" "$FILE" || Fatal "Could not replace '$FILE'."
done
The LANG and LC_ALL are used to set the POSIX aka C locale, just in case you happen to have filenames with non-UTF-8 byte sequences in them. If you use an UTF-8 locale (and most of us do), the tools will abort if they encounter a non-UTF-8 byte sequence in a file name or other string. Setting the locale to POSIX tells tools to treat all names and strings as eight-bit byte sequences.
Note that this affects the
sed , too. In the POSIX locale,
ä does not match
[a-z]. In the
en_US.UTF-8 locale (and all other UTF-8 locales), it does! If you need or want that behaviour, you can always just add
LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 before the
sed command, on the same line, to change the locale only for the sed command.
The loop body will first copy the original file into the temporary directory, preserving all metadata (from ownership and mode, up to extended attributes). Ownership is only preserved if the user running the command is allowed to. This copy is done not to save the data, but to save the metadata; this happens to be the easiest way to copy
all metadata.
The
sed command overwrites the contents of the temporary copy. If it fails, the original file will be intact. Redirecting to the file does not change its metadata (other than size and modification timestamp).
Finally, the original file is replaced with the edited copy. If
$tempdir is on the same filesystem, this is guaranteed to leave you with either the original file, or the new file, but never a broken copy. (For different filesystems, it depends on
mv implementation; I believe Coreutils mv has the same guarantee across filesystems if using ext2/ext3/ext4/xfs/reiserfs.)
While the above sequence may look a bit cumbersome, it practically guarantees it will always Do The Right Thing. If it fails, the original file is kept intact. If it succeeds, only the contents of the file are changed (although actually the entire file is replaced). For example, SELinux security context, POSIX ACLs, and all extended attributes the file might have, should stay intact.
Moreover, if it fails for any reason, you will always get an e-mail message describing the reason. You could even add
Code:
logger -p 'local.error' -i -t 'MyScript' "$@"
just before the
exit 1 in the
Fatal function body, to log the error message in the system log, in addition to sending an e-mail. See
man logger for details on its use.
Hope this helps,