10

Is it possible to customize the Bash error messages attributes?

For example, is it possible to edit the .bash_profile to get the following Bash error message

-bash: cd: foo: No such file or directory

in red?

5 Answers 5

4

Sadly, there is no PS prompt variable to control how bash errors are presented.

stderred is a comprehensive solution for this type of problem, but it will not work without modification as it has a hard-coded exclude for bash (read on for good reasons why). Also it's somewhat intrusive (DLL injection).

hilite requires you to use it as a wrapper, this won't work for bash builtin commands like cd.

Your question asks specifically about bash error messages, these are send to stderr, but stderr is also shared with any child processes, i.e. any other commands. I'm not sure if you want to distinguish the two.

A hidden issue here is that bash itself writes the prompt and your input (echo) to stderr. To prove:

bash              # start a sacrificial shell
exec 2> tmpfile   # change stderr to a file
ls                # you will see no prompt, and no command echo
... 
exit              # quit sacrificial shell
cat tmpfile       # contains PS1 prompt, your commands (and any errors too)

Builtins call (or at least should call) the builtin_error() internal function to print errors, this unconditionally calls fprintf() to stderr, so the options are few.

Without jumping through hoops, or patching bash, a simple way of highlighting errors is:

function _t_debug() 
{
    if [ "${BASH_COMMAND:0:6}" != "_t_err" ]; then
        _lastcmd="$BASH_COMMAND"
    fi  
}
function _t_err() 
{
    local rc=$1 nn _type _argv
    #shift; pipe=($*)
    #if [ ${#pipe[*]} -gt 1 ]; then
    #    for ((nn=1; nn<=${#pipe[*]};nn++));do
    #        rc=${pipe[$((nn-1))]}
    #        echo -n "[$nn]=$rc ";
    #        ((rc >=128)) && echo -n "($((rc-128))) "
    #    done
    #fi

    read -a _argv <<< ${_lastcmd}
    _type=$(type -t "${_argv[0]}")

    if [ -n "$_lastcmd" ]; then
        tput setf 4
        printf 'Error %s: "%s"' "${_type}" "${_lastcmd:-unknown command}"
        tput sgr 0
        printf "\n"
    fi

    ((rc >=128)) && echo "[rc=$rc ($((rc-128)))]" ||
    echo "[rc=$rc]"
}
trap '_t_err $? ${PIPESTATUS[*]}' ERR
trap '_t_debug' DEBUG

This uses the bash DEBUG trap to cache each command line before execution, and the ERR trap to output the return codes if non-zero. This won't though for certain bash builtins (specifically compound commands: while/case/for/if and more, see man page).

I use a variation on this in my .profile, though I use the pipe[]/PIPESTATUS[] bit commented out above, it's not compatible with a DEBUG trap as presented above. If you comment out the trap DEBUG then you can use it to show the return code of each command in a pipeline.

(Also, since it's referenced, the command_not_found function hook is bash-4.0+.)

2

Below solution works for what you want:

# whatever_command 2> >(while read line; do echo -e "\e[01;31m$line\e[0m" >&2; done)

Basically, above command prints stderr in red.

Check below screen shot:

enter image description here

I do not know how to make this permanent. However, this should definitely give you some pointers.

2

You might want to check out stderrred for a more permanent sollution.

1

Put this into your ~/.bashrc or ~/.bash_profile file for a permanent fix, or, if you want it to be temporary, just type this into bash.

command_not_found_handle () { 
  tput bold;
  tput setaf 1;
  echo "$0: $1: command not found"; 
  tput sgr0; 
}

It will make bash output the command not found error in bold, and red. Go ahead and take out tput bold; to make it so that the error message is not bold, or change the tput setaf 1 to another number for a different color. Hope it helps!!

EDIT

Here is a new and improved version of the above:

command_not_found_handle () {
  echo -e "\e[1;31m$0: $1: command not found\e[0;0m";
  return 127; #return bash's error code for command not found
}

EDIT 2

If the name is neither a shell function nor a builtin, and contains no slashes, bash
searches each element of the PATH for a directory containing an executable file by that
name. Bash uses a hash table to remember the full pathnames of executable files (see hash
under SHELL BUILTIN COMMANDS below). A full search of the directories in PATH is performed
only if the command is not found in the hash table. If the search is unsuccessful, the
shell searches for a defined shell function named command_not_found_handle. If that
function exists, it is invoked with the original command and the original command's
arguments as its arguments, and the function's exit status becomes the exit status of the
shell. If that function is not defined, the shell prints an error message and returns an
exit status of 127.

Taken from bash's manpage

So if this does not work, maybe your shell is not true bash, or it is a modified version of bash.

3
  • Thanks BenjiWiebe, your solution seems very interesting, but unfortunately, it doesn't work, even a more simple solution, like command_not_found_handle () { echo "Error..." }: I still get the basic -bash: foo: command not found? I'm using a Mac OSX operating system.
    – Hassen
    Jan 26, 2013 at 16:31
  • @Hassinus Are you really using bash? See edit 2
    – BenjiWiebe
    Jan 26, 2013 at 19:39
  • 1
    @BenjiWiebe: Your solution only traps one specific error, a failure in command lookups. The example in the question is not such an error.
    – chepner
    Jan 26, 2013 at 21:28
0

Maybe you can try hilite - I think this is one of most simple solutions.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .