November 1, 2014
 
 
RSSRSS feed

Linux Commands: Making Bash Error Messages Friendlier - page 2

Trapping and Befriending Error Messages

  • March 31, 2010
  • By Akkana Peck

For instance, if the user typed the name of a file instead of an executable program, wouldn't it be handy to check the type of the file and suggest programs they might have intended? Something like this:

$ /tmp
bash: /tmp: is a directory
status was 126
Perhaps you meant: cd /tmp

$ schedule.html
bash: ./schedule.html: Permission denied

schedule.html is an HTML file. Did you want to run: firefox schedule.html

That's easy to do. You can check for a directory with the shell construct if [[ -d. If it's a file, you can use the file command to guess what type it is.

  if [[ -e $cmd ]]; then
    if [[ -d $cmd ]]; then
      echo "Perhaps you meant: cd $cmd"

    elif [[ ! -x $cmd ]]; then
      echo ""
      filetype=$(file $cmd)
    
      # HTML must come before text, since file says "HTML document text"
      if  [[ $filetype = *HTML* ]]; then
        echo "$cmd is an HTML file. Did you want to run: firefox $cmd"
      [ ... ]    
You can use similar methods for each file type you want to handle -- you might want to suggest apps the user could call for image files, text files, movies and so on.

What about that case of a filename missing a slash? That's easy to check for too: look through the list of arguments, check whether each file exists, and if it doesn't, see if adding a slash to the beginning gives you the name of an existing file:

  if [[ $status -eq 2 ]]; then
    # loop over args looking for the first one that might be missing a slash
    for f in $cmd "${args[@]}"; do
      if [[ $f = */* && ! -e $f ]]; then
        if [[ -e /$f ]]; then
          echo ""
          echo "$f doesn't exist, but /$f does."
          echo "Did you forget a leading slash?"
          return
        fi
      fi
    done

You can even check for cases where the file isn't readable, and suggest that the user might need to be root.

  if [[ -e $cmd ]]; then
    if [[ -d $cmd ]]; then
      echo "Perhaps you meant: cd $cmd"
    elif [[ ! -x $cmd ]]; then
      if [[ ! -r $cmd ]]; then
        echo ""
        echo "$cmd is a file but it's not readable or executable."
        echo "Maybe you need to be root?"

Akkana Peck is a freelance programmer and writer, and author of the book Beginning GIMP: From Novice to Professional. She also fiddles with way too many kernels and Linux distros.

A sample error-handling script

Here's a basic example of a bash error handler. You can add this to the end of your .bashrc; or save it as a separate file, like ~/.bash-error, then add this line to your .bashrc:

. $HOME/.bash-errs

Here's the script:

#
# Offer slightly friendlier error messages for certain types of errors.
#
function err_handle {
  status=$?

  if [[ $status -ne 2 && $status -ne 126 && $status -ne 127 ]]; then
    return
  fi

  # Ucky pipeline which is, amazingly enough,
  # the only way to get the last typed command from bash.
  # fc  -n -l -1 doesn't always have the command yet,
  # !! doesn't work from inside functions
  # and BASH_COMMAND gets confused by functions like ls().
  lastcmd=$(history | tail -1 | sed 's/^ *[0-9]* *//')

  # cool way to split a string into component words:
  read cmd args <<< "$lastcmd"

  # Handle possible errors involving forgetting a leading slash if the
  # command was okay but the error was 2, "no such file or directory".
  if [[ $status -eq 2 ]]; then
    # loop over args looking for the first one that might be missing a slash
    for f in $cmd "${args[@]}"; do
      if [[ $f = */* && ! -e $f ]]; then
        if [[ -e /$f ]]; then
          echo ""
          echo "$f doesn't exist, but /$f does."
          echo "Did you forget a leading slash?"
          return
        fi
      fi
    done
    return
  fi

  if [[ -e $cmd ]]; then
    if [[ -d $cmd ]]; then
      echo "Perhaps you meant: cd $cmd"
    elif [[ ! -x $cmd ]]; then
      if [[ ! -r $cmd ]]; then
        echo ""
        echo "$cmd is a file but it's not readable or executable."
        echo "Maybe you need to be root?"
        echo "You could try sudo less $cmd"
        echo "or sudo $(myeditor) $cmd"
        return 127
      fi

      #
      # By now, we know it's a file and it's readable.
      # Figure out the file's type, and print appropriate messages:
      #
      echo ""
      filetype=$(file $cmd)
    
      # HTML must come before text, since file says "HTML document text"
      if  [[ $filetype = *HTML* ]]; then
        echo "$cmd is an HTML file. Did you want to run: firefox $cmd"
    
      elif  [[ $filetype = *text* ]]; then
        echo "$cmd is a text file. Did you want to run:"
        echo "    less $cmd"
        echo "    vim $cmd"
    
      elif  [[ $filetype = *image* ]]; then
        echo "$cmd is an image file. Did you want to run:"
        echo "    pho $cmd"
        echo "    gimp $cmd"

      else
        # "file" gives terribly complex output for MS Office documents
        # so get the mime type to detect those:
        mimetype=$(xdg-mime query filetype $cmd | sed 's/;.*$//')
        if [[ $mimetype == application/msword ]]; then
          echo "$cmd is a Microsoft Word file."
          echo "Perhaps run: ooffice $cmd"
        elif [[ $mimetype =~ application/.*ms- ]]; then
          echo "$cmd is a file of type"
          echo "  $mimetype (Microsoft)."
          echo "Perhaps try: ooffice $cmd"

        else
          #
          # Unknown file type -- bomb out.
          #
          echo "$cmd is a file of type $mimetype."
          echo "What do you want to do with it?"
        fi
      fi

    else
      echo "Hmm, $cmd exists and is executable -- not sure what went wrong"
    fi
#  else
#    echo "Sorry, $cmd doesn't exist"
    # If we want to be REALLY nice we could look for similarly named progs.
    # But it turns out Ubuntu's command-not-found-handle does that already.
  fi
}

# Trap errors.
trap 'err_handle' ERR
Sitemap | Contact Us