Linux, Unix, /etc

Danger Will Robinson! You are now entering a condescending Unix user zone!
Sponsored links (requires javascript):

Getting the Most From Your Shell

Preface

What is the most-used program on every Unix system, by every Unix user? It's the shell. The shell is ubiquitous. Yet, people often learn the few commands they absolutely must: just what they need to, say, read mail, start their editor, and print documents, and leave it at that. But the shell can do far, far more than start applications. This article will show you how to start getting more from your shell.

Introduction

While the rest of the world points and clicks in a scary little world of icons, all alike, we in the world of Unix get to use a good old-fashioned CLI, or Command Line Interface. One reason why the command line has remained so pervasive in Unix environments is that the implementation, the Unix shell in its various incarnations, is very good. It allows the user to use the tools provided to build new tools. I'll focus on three areas: editing the command line, using alias and functions as shortcuts, and a little bit about shell programming.

What Shell

Of course, there is more than one shell. To keeps things simple, I'll talk about what I know. That means shells in the Bourne shell tradition, and particularly three: /bin/sh, /bin/ksh, and /bin/bash. I'll try to focus on features that all three share, though inevitably, particularly for interactive use, I'll have to discuss features that the older shell doesn't have, notably job control and command-line editing. I won't cover the C shell. I've never used it, it's not well-regarded for anything but interactive use (and one of the purposes of this article is to blur the line between interactive and batch).

Command Line Editing

The first way to make the shell easier to use is to set up command-line editing. Two types are commonly-available in shells derived from the Korn shell. Those using the vi commands, and those using emacs edting keystrokes. They are very different, and users who swear by one will take pains to avoid the other. In both ksh and bash, to activate vi command-line editing, type

set -o vi

Then you can use more or less the same set of command keys that you have available in vi. In both ksh and bash, to activate emacs command-line editing, type

set -o emacs

Then you can use a subset of emacs command keys, and treat the command line as a sort of one-line emacs buffer.

Shell Options

Setting which type of command line editing you prefer is just one instance of setting shell options, which control may aspects of your shell's behaviour. You set shell options using set -o. Options can be unset using set +o.

The man page for your shell will list all the options; here, I'll focus on some that I think you'll find useful.

notify

Print job notification messages asynchronously, instead of just before the prompt. Both ksh and bash support this option.

noclobber

In both ksh and bash, this prevents > redirection from overwriting existing files. With this set, >| must be used to force an overwrite, else the shell will tell you "cannot create [file]: File exists".

bgnice

Background jobs are run with lower priority. This is a ksh option, and is not in bash.

ignoreeof

The shell will not exit on end-of-file (^D), "exit" must be typed. Actually, to avoid infinite loops, pdksh *will* exit if eof is read 13 times in a row. Bash is more sophisticated. To quote the man page:

"If set, the value is the number of consecutive EOF characters typed before bash exits. If the variable exists but does not have a numeric value, or has no value, the default value is 10. If it does not exist, EOF signifies the end of input to the shell."
nohup

Do not kill running background jobs when a login shell exists. This is another ksh option that isn't in bash.

Aliases

How do we use the shell? In day to day use, our most common use is in typing in commands, one after the other:

$ vi article
...
$ cat todo
...
$ date

and so on and so forth. Some commands are simple, other commands are quite complex. Where the command takes some typing effort, it is worthwhile to make a short cut. The easiest way to do this is to use what we call an "alias". Making an alias is straighforward. At the simplest level, to make our short cut, it is as though we simply replaced one command by another. A simple example from my own system is

alias pg=less

This saves me two keystrokes every time I want to examine a file using the "less" pager. Trivial? Maybe. But it's something I use many times each day.

An alias is a way of giving a nickname to a command or series of commands. The lefthand token is expanded to the righthand value by the shell, just the same as shell wildcards are expanded. So, for example, if I define the alias

alias rm=rm -i'
'

then when I type

$ rm foobar

the shell actually runs

$ rm -i foobar

which prompts me before doing anything rash like, oh, removing a file.

Aliases are also handy as a way of running a short series of shell comamnds that might otherwise have to be put in a shell script. If they are short, consider implementing such things as an alias rather than an script, saving load time. Aliases are "inherited". That is to say, in:

alias vi='vi -F'
alias vp="vi $HOME/.profile"

The 'vi' in 'vp' will be expaned to 'vi -F'. A useful way of seeing how much work I have to do is setup by the following:

alias la='ls -tr1 $HOME/articles/wip'

Another use of aliases is to save one remembering arcane syntax. why bother with "chmod +x" or "chmod 0755" or whatever, when a two-character alias does what you want i.e. makes a file exectuble?

alias cx="chmod +x $*"

Sometimes, one grows used to certain comamnds. On some flavours of Unix, for example, the pager program is neither more nor less, but pg. Why bother getting used to typing a new command. Simply use an alias:

alias pg='less'

Something I find useful is to have my browser point at a default location if I don't give a URL on the command line.

alias lynx="lynx -cache 100 ${1:-$HOME/homepage/index.html}"

Functions

The shell is a programming langauge, remember, so it's only natural that it should have functions. The syntax for functions is simple. A function defined in the shell looks not dissimilar to the way it would be defined in the C programming language: the function name, followed by a pair of parentheses, then the body of commands that make up the function enclosed in braces.

Functions are a natural progression from aliases, and avoid the overhead of a shell script at the cost of a bigger environment. Which is as good a place as any to mention, that this stuff doesn't come free. The more stuff you do in your init files — setting variables, defining fucntions, etc — the bigger your environment— an area of memory that each new process you start will copy — gets. For example, on my system

$ env | wc -c 3441

3,441 bytes. Quite a bit. You shouldn't notice preformance degradation on a modern machine, but this is something to be aware of. If most of your enivronment is only for interactive use, make sure that it isn't defined for non-interactive shells. You can do this by adding the lines

case $- in
        *i*) ;;
	*) return 0;;
esac

A little set of functions I find useful is this, for simplying job control.

b() { bg %$1 } f() { fg %$1 } k() { kill %${1:?must give job number} } twep() { kill -9 ${1:?need process number for termination with extreme prejudice} } ) What have we here? Well, b puts a job in the background, f brings it into the foreground again, and k kills it — all using handly job numbers rather than long-winded process numbers. Finally, twep is a bit of whimsy. Notice that arguments are passed to functions using the standard shell convention of $n, where n is 1-9, and that unlike C, the arguments aren't declared between the parenthese following the function name (in fact, those parentheses are really just a bit of syntactic sugar).

The boundary between aliases, fucntions and shell scripts is blurred. Given aliases and fucntions, making the jump to shell scripting is not difficult.

As my final example, I'll show a quite complicated function that could just as well be put in an executable file and called a shell script.

The MH mailer stores draft e-mails as files in a directory, just as it stores all other e-mails. One then uses comp -use <filename> to work on a particular draft. However, I often have no intention of sending an e-mail straight away, and would rather just fire up my editor on the message file. So, wrote a simple script, in effect adding a new command to MH. The script works as other MH commands by taking as argument the number of a mail message to edit. So now I can look at my drafts directory using the "drafts" command, the edit the message I want with, say, "vd 20". Here's the script:

vd()
{
case $1 in
    "");;
    last) vi $(ls $HOME/Mail/drafts/[1-9]*|sort -t '/' +5 -n|tail -1) ;;
    first) vi $(ls $HOME/Mail/drafts/[1-9]*|sort -t '/' +5 -n|head -1) ;;
    [0-9]*) vi $HOME/Mail/drafts/$1;;
    *) echo 'vd: usage: vd [first|last|<msg-nr.>]';;
esac
}

Here, we are in fact doing simple programming, using a case statement to decide what to do. $1, as we know, is the first argument to the function, and depending on its value — last, first, a number, or anything else — we execute the appropriate line of shell commands, and then, skipping the rest, end up at the end of the function. Those shell commands look complex, but breaking them down, they become clear. The lines for last and first are the most complicated, and since they are very similar,.we'll consider them together. First, we build a list of path names from the draft directory:

ls $HOME/Mail/drafts/[1-9]*

Then, we sort them in numerical order on the file name:

sort -t '/' +5 -n

(-t identifies the field seperator, +5 says *4th* field (we start counting with 0, -n says sort numerically). Now, we grab either the last or the first line of this list — the first or last draft e-mail, respectively.

tail -1

OR

head -1

Now that we've got our file name, we want to run the editor on it. We do this by passing the output of our long command to the argument list of the editor command, by enclosing the whole thing in $(...). And there it is.

The other command lines are much simpler. If we're given a numerical argument, then we just build the pathname and hand it to vi directly. And if we have anything else, we don't know what to do, so we complain and don't try to do anything!

I can't hope to provide a shell programming tutorial in the space available, instead, I'll give a practical example, and hope that this inspires you to learn more.

[Box?] A Note on init Files

Of course, we don't type our aliases and fucntions in anew each time we start up the shell. An interactive shell reads a number of init files on startup, and it is here that's the best place to put our definitions.

What init file goes where depends on what shell you are using. In the beginning, there was the Bourne shell, /bin/sh. By the way, you won't find this if you go looking on your Linux box; there, /bin/sh is really /bin/bash, the GNU Bourne-Again SHell. This had just one init file, ~/.profile.

Nowadays, there are at least two profile files for all shells derived from /bin/sh: one global file in /etc, /etc/profile; and one user-specific file in the user's home directory, ~/.profile.

Then, there are shell-specific init. files.

For example, with pdksh we have; ~/.kshrc More properly, this is the default value of the environment variable ENV, which points to a file to source; we can set ENV to signify any file we like. In any case, every time a new interactive shell is started, this file will be sourced.

Similarly, bash has, in addition to the two standard files, a number of others in the user's home directory.

Bash's use of init files depends on the type of shell it is invoked as. Bash knows of three types of shell: two interactive, login, and non-login, and one non-interactive.

For login shells, these rules apply: if /etc/profile exists, it is sourced. Then, bash looks for several files in the user's home directory, and sources the first one it finds. Firstly, it looks for ~/.bash_profile If this file does not exist, it looks for ~/.bash_login, and sources that instead. Finally, if neither are found, bash will try to source ~/.profile. On exit, if ~/.bash_logout exists, it is sourced.

For non-login interactive shells, if ~/.bashrc exists, it will be sourced. And that's it!

For non-interactive shells, if the environment variable ENV is set, the file it points to is sourced, as if the command

if [ "$ENV" ]; 
then 
. $ENV; 
fi 

had been executed. However PATH is *not* used to search for the pathname given in ENV.

users of ksh can get a similar functionality to the bash .bash_logout file by using the following trick. Put this line into .profile:

trap '. $HOME/.sh_logout; exit' 0

Now, when the shell exits, it will source the .sh_logout file. The same trick can also be used for bash when running as a non-login shell. Put the line into .bashrc.

Note that commands in these init files are "sourced"; that is, commands affect the current evironment (normally, executing a command doesn't affect the current i.e. parent environment — this is why shell scripts to cd to a directory don't affect the current directory of your login shell).

Resources

For pdksh, see For bash, see There are plenty of books on using the shell. The classic chapters in The Unix Programming Environment are aging but evergreen. Another great resource that every shell user should have is Unix Power Tools from O'Reilly, now in its second edition.

Paul Dunne 1999


[back to Linux, Unix, /etc]



Copyright © 1995-2007 Paul Dunne,

Sponsored links (requires javascript):