Simple Linux upgrade script in Bash revision 2
Don't use non-portable terminal escape codes
The tput program exists for exactly this purpose:
red=$(tput setaf 1)
green=$(tput setaf 2)
yellow=$(tput setaf 3)
nocolor=$(tput sgr0)

(I've downcased your variable names, to avoid surprising conflicts with environment variables)
Inline the single use functions
show_success and show_error_and_exit are laudable, and do help readability, but they are only used once each, and may be clearer if written directly into error_handler.  Either way, their output really ought to go to the error stream: &gt;&amp;2.
Reduce duplication
Look at this pattern:
echo -e &quot;\n${GREEN}Step 1: configure packages${NOCOLOR}&quot;
echo -e &quot;${YELLOW}dpkg --configure -a${NOCOLOR}&quot;

sudo dpkg --configure -a

error_handler $?  1

This structure is repeated throughout the program, but it can be refactored.  Let's write the variable parts using variables:
echo &quot;${green}Step ${step}: ${description}${nocolor}&quot;
echo &quot;${yellow}${command}${nocolor}&quot;

$command

error_handler $?  $step

There's three things we need: a step number, description, and the command to run.  The command needs to be an array; that's easy to arrange by passing it as all the arguments after description.  We can also inline the error_handler:
do_action()
{
    step=$1; shift
    description=$1; shift
    printf &quot;${green}Step %s: %s\n&quot; &quot;$step&quot; &quot;$description&quot;
    printf &quot;${yellow}&quot;
    printf '%q ' &quot;$@&quot;
    printf &quot;${nocolor}\n&quot;

    if &quot;$@&quot;
    then
        printf &quot;${green}Step %s: SUCCESS${nocolor}\n&quot; &quot;$step&quot;
    else
        err=$?
        printf &quot;${red}Step %s: FAILED${nocolor}\n&quot; &quot;$step&quot; &gt;&amp;2
        exit $err
    fi
}

This function is then used as:
i=0
do_action $((++i)) &quot;configure packages&quot; dpkg --configure -a
do_action $((++i)) &quot;fix broken dependencies&quot; apt-get install --fix-broken
do_action $((++i)) &quot;update cache&quot; apt-get update
do_action $((++i)) &quot;upgrade packages&quot; apt-get upgrade
do_action $((++i)) &quot;upgrade distribution&quot; apt-get dist-upgrade
do_action $((++i)) &quot;remove unused packages&quot; apt-get --purge autoremove
do_action $((++i)) &quot;clean up&quot; apt-get autoclean

You might choose to make the step number a global variable and increment it inside the function; I haven't done that, because global variables tend to proliferate and make the program hard to reason about.
Don't sudo in a script
Many calls to sudo is a sign that the entire script needs superuser capabilities, so it's better if the user runs the script as root.  You could automate this (though I would personally just error out):
test $EUID = 0 || exec sudo $0 &quot;$@&quot;


Full program
#!/bin/bash
set -e -u

red=$(tput setaf 1)
green=$(tput setaf 2)
yellow=$(tput setaf 3)
nocolor=$(tput sgr0)

do_action()
{
    step=$1; shift
    description=$1; shift
    printf &quot;${green}Step %s: %s\n&quot; &quot;$step&quot; &quot;$description&quot;
    printf &quot;${yellow}&quot;
    printf '%q ' &quot;$@&quot;
    printf &quot;${nocolor}\n&quot;

    if &quot;$@&quot;
    then
        printf &quot;${green}Step %s: SUCCESS${nocolor}\n&quot; &quot;$step&quot;
    else
        err=$?
        printf &quot;${red}Step %s: FAILED${nocolor}\n&quot; &quot;$step&quot; &gt;&amp;2
        exit $err
    fi
}

i=0
do_action $((++i)) &quot;configure packages&quot; dpkg --configure -a
do_action $((++i)) &quot;fix broken dependencies&quot; apt-get install --fix-broken
do_action $((++i)) &quot;update cache&quot; apt-get update
do_action $((++i)) &quot;upgrade packages&quot; apt-get upgrade
do_action $((++i)) &quot;upgrade distribution&quot; apt-get dist-upgrade
do_action $((++i)) &quot;remove unused packages&quot; apt-get --purge autoremove
do_action $((++i)) &quot;clean up&quot; apt-get autoclean