Sunday, April 4, 2010

The command line! The bane of the novice Linux user! It's so useful -- yet it can be challenging to learn.

The error messages don't help much. "Command not found." "Permission denied." As a newbie, you need to know more.

Isn't that the right command? Why was permission denied? How are you to figure out what the real problem was? And why can't the shell help you with that?

Ubuntu has taken some steps in that direction already. They've set up the bash shell so that if you get "Command not found", most of the time you'll also see suggestions on what you might have meant: commands that are spelled similarly, or commands that aren't installed along with which package you need to install to get them.

It looks like this:

$ catt /etc/fstab

No command 'catt' found, did you mean:
Command 'cat' from package 'coreutils' (main)
Command 'cant' from package 'swap-cwm' (universe)
catt: command not found

It's an excellent step. Perhaps still not 100% clear -- you still need to know what those packages are and how to install them -- but it's a good start!

But what about other errors, like the all too common "Permission denied"? Ubuntu's error handling uses a function built into bash for that specific purpose, a function called command_not_found_handle that can't be used for other types of errors.

Happily, bash has a more general error trapping mechanism that you can use to handle any kind of error the user might make.

The key is bash's trap command. First you define an error handler function:

function err_handle {
echo "Place your error handling code here"
}

Then use trap to tell the shell to call your error handler any time it gets an error:

trap 'err_handle' ERR

In the error handler, you can check the shell variable $? to find out what the error code was. So the first step is to figure out which errors you need to catch.

How do you do that? The easiest way is to write a very simple error handler that just prints the numeric code.

Then split the line into the command (the first word) and arguments (everything else). You can use bash's read command for that:

read cmd args <<< "$lastcmd"

Now you know the error code and the command. The rest is just a matter of figuring out what sorts of errors your users are likely to hit, then offering them useful suggestions in each case.

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:

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?"

A sample error-handling scriptHere'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: