How to make cron notify the user about a failed command by redirecting its output to stderr only when it fails (non-zero exit code)

Rate this post

Cron jobs can handle everything from system maintenance to running backups, but monitoring their success or failure can be difficult. One effective way to handle cron job errors is by utilizing a Bash script that sends the output to stderr only if the job fails, which generally makes cron to notify the user about the error that occurred.

Script overview

The script provided below ensures that any command passed to it will redirect its output to a temporary file. If the command fails (i.e., returns a non-zero exit code), the script sends the content of the temporary file to stderr, which causes cron to notify the user.

Here is the complete script to be installed in /usr/local/bin/outonerror:

#!/usr/bin/env bash
# Description:
# Redirect the command's output to stderr only if the command fails (non-zero
# exit code). No output is shown when the command succeeds.
#
# Author: James Cherti
# License: MIT
# URL: https://www.jamescherti.com/cron-email-output-failed-commands-only/

set -euf -o pipefail

if [[ "$#" -eq 0 ]]; then
  echo "Usage: $0 <command> [args...]" >&2
  exit 1
fi

cleanup() {
  if [[ "$OUTPUT_TMP" != "" ]] && [[ -f "$OUTPUT_TMP" ]]; then
    rm -f "$OUTPUT_TMP"
  fi

  OUTPUT_TMP=""
}

trap 'cleanup' INT TERM EXIT
OUTPUT_TMP=$(mktemp --suffix=.outonerror)

ERRNO=0
"$@" >"$OUTPUT_TMP" 2>&1 || ERRNO="$?"
if [[ "$ERRNO" -ne 0 ]]; then
  cat "$OUTPUT_TMP" >&2
  echo "$0: '$*' exited with status $ERRNO" >&2
fi

cleanup
exit "$ERRNO"
Code language: Bash (bash)

To use this script with a cron job, save it as /usr/local/bin/outonerror and make it executable by runnning:

chmod +x /usr/local/bin/outonerrorCode language: plaintext (plaintext)

Integration with Cron

Cron sends an email by default whenever a job produces output, whether standard output or error output. This is typically configured through the MAILTO environment variable in the crontab file. If MAILTO is not set, cron sends emails to the user account under which the cron job runs. Cron will only be able to send emails if the mail transfer agent (e.g., Postfix, Exim, Sendmail) is configured properly.

Here is how to schedule the cron job to use the outonerror script:

MAILTO="your-email@example.com"
* * * * * /usr/local/bin/outonerror your_command_hereCode language: plaintext (plaintext)

With this setup, the cron job will execute your_command_here and only send an email if it fails, thanks to cron’s default behavior of emailing stderr output to the user.

Conclusion

This script is a simple yet effective solution for improving cron jobs by ensuring that the user is notified only when something goes wrong. It reduces unnecessary notifications for successful executions and provides clear error messages when failures occur.

Bash shell: Perform tab-completion for aliases (bash-completion)

5/5
# Author: James Cherti
# License: MIT
# Requirements: bash-completion
# Description: Perform tab-completion for aliases in Bash (bash-completion).
# URL: https://www.jamescherti.com/bash-shell-perform-tab-completion-for-aliases/
#
# Add the following function to ~/.bashrc :

alias_completion() {
  local func_name='alias_completion'
  if [[ $# -lt 2 ]]; then
    echo "Usage: $func_name <cmd> <alias> <alias2> <...>" >&2
    return 1
  fi

  local cmd; cmd="$1"
  shift

  # Load the completion
  if ! type _completion_loader >/dev/null 2>&1; then
    echo "Error: $func_name: '_completion_loader' was not found." >&2
    return 1
  fi

  _completion_loader "$cmd"

  if ! complete -p "$cmd" >/dev/null; then
    echo "Error: $func_name: 'complete -p $cmd' failed." >&2
    return 1
  fi

  # Add aliases
  local alias
  for alias in "$@"; do
    complete_cmd=$(complete -p "$cmd" 2>/dev/null | sed -e 's/[[:space:]][^[:space:]]\+$//')
    complete_cmd="${complete_cmd} $alias"

    if ! ( echo "$complete_cmd" | grep -P '^\s*complete\s' >/dev/null 2>&1 ); then
      echo "Error: $func_name: alias '$alias': '$complete_cmd' is an invalid command." >&2
      return 1
    fi

    eval "$complete_cmd"
  done

  return 0
}Code language: Bash (bash)

Examples of aliases:

alias s='ssh'
alias_completion ssh s

alias g='git'
alias_completion git gCode language: Bash (bash)

Bash shell: A better version of the default bash built-in command “cd”

5/5
#!/usr/bin/env bash
# Author: James Cherti
# License: MIT
# URL: https://www.jamescherti.com/shell-bash-replacement-bash-cd-change-directory/
#
# Description:
# 1. 'cd path/to/file' will change the directory to 'path/to'
#    (the parent directory of 'file').
#
# 2. 'cd path/to/dir with spaces' will change the directory to
#    "path/to/dir with spaces".
#
# 3. 'cd file:///home/user' will change the directory to "/home/user".
# 
# 4. You can switch to the previous directory with 'popd' or 'cd -'.
#
# Add the following function and alias to ~/.bashrc :
#

_better_cd() {
  # Previous directory ('cd -')
  if [[ $# -eq 1 ]] && [[ $1 = '-' ]]; then
    popd >/dev/null || return 1
    return 0
  fi

  # Join paths
  local path
  if [[ $# -eq 0 ]]; then
    path="$HOME"
  else
    path=$(echo "$1" | sed -e 's/^file:\/\///')
    shift

    local item
    for item in "$@"; do
      path="${path} ${item}"
    done
  fi

  # Checks
  local errno=0
  if [[ -f "$path" ]]; then
    path=$(dirname "$path")
  fi

  if ! [[ -d "$path" ]]; then
    echo "$(basename "$0"):" "cd: $path: No such file or directory" >&2
    return 1
  fi

  # Change the directory
  local oldcwd; oldcwd=$(pwd)
  pushd . >/dev/null || return 1
  builtin cd "$path" >/dev/null 2>&1 || errno=1
  if [[ $oldcwd = "$(pwd)" ]]; then
    popd >/dev/null || return 1
  fi

  return "$errno"
}

alias cd=_better_cdCode language: Bash (bash)