From: www.itworld.com
March 29, 2001 —
While the rest of the world points and clicks in a world of icons, we in the world of Unix get to use a good old-fashioned command-line interface (CLI). One reason the command line has remained so pervasive in Unix environments is that its implementation -- the Unix shell in its various incarnations -- is very good. With it, the user can build new tools with the ones provided.
To keep things simple, I'll talk about three shells in particular, all in the Bourne shell tradition: /bin/sh, /bin/ksh, and /bin/bash. I'll try to focus on features that all three share, though inevitably I'll have to discuss features that the older shell doesn't have, notably job control and command-line editing.
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 editing 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
This allows you to use the same set of command keys you have available in vi.
In both ksh and bash, to activate Emacs command-line editing, type:
set -o emacs
This allows you to 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 the shell options that control many aspects of your shell's behavior. 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 a few that I think you'll find useful.
notify: Prints 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, or else the shell will tell you cannot create [file]: File exists.
bgnice: Runs background jobs with lower priority. This is in ksh, but not in bash.
ignoreeof: Determines that shell will not exit on end-of-file (^D); exit must be typed. To avoid infinite loops, pdksh will exit if EOF is read 13 times in a row. The implementation in 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: Does not kill running background jobs when a login shell exists. This is another ksh option that isn't found in bash.
Aliases
How do we use the shell? Our most common use is for typing commands, one after the other:
$ vi article ... $ cat todo ... $ date
and so on. When the command takes some typing effort, it's worthwhile to make a shortcut by using what we call an alias. Making an alias is straightforward. At the simplest level, it's as though we're replacing one command with another. Here is an example from my own system:
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 a series of commands. The left-hand token is expanded to the right-hand value by the shell, the same way 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 a handy way of running a short series of shell commands that might otherwise have to be put in a shell script. If you only have a few commands to run, consider implementing them as an alias rather than a script.
Aliases are inherited. That is to say, in
alias vi='vi -F' alias vp="vi $HOME/.profile"
the vi in vp will be expanded to 'vi -F'.
Here is a useful way of seeing how much work I do:
alias la='ls -tr1 $HOME/articles/wip'
Aliases save one from having to remember arcane syntax. Why bother with chmod +x or chmod 0755 when a two-character alias does what you want?
alias cx="chmod +x $*"
Sometimes one grows used to certain commands. On some flavors of Unix, for example, the pager program is neither more nor less, but pg. Why bother getting used to typing a new command?
alias pg='less'
Something I find useful is to have my browser point to 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 language, so it's only natural that it should have functions. The syntax for functions is simple. A function defined in the shell looks similar to the way it would be defined in C: 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 they avoid the overhead of a shell script at the cost of a bigger environment. This is as good a place as any to mention that this stuff doesn't come free. The more you do in your init files -- setting variables, defining functions -- 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
is 3,441 bytes. You shouldn't notice performance degradation on a modern machine, but it's something to be aware of. If most of your environment is only for interactive use, make sure it isn't defined for noninteractive shells. You can do this by adding the lines:
case $- in
*i*) ;;
*) return 0;;
esac
Here's a little set of functions I find useful for simplifying 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 handy 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 a digit one through nine. Unlike C, the arguments aren't declared between the parentheses following the function name. In fact, those parentheses are really just a bit of syntactic sugar.
The boundaries between aliases, functions, and shell scripts are blurred. Given aliases and functions, jumping to shell scripting is not difficult.
My final example is a complicated function that could just as well be put in an executable file and called a shell script.
The MH mailer stores draft emails as files in a directory, just as it stores all other emails. One then uses comp -use to work on a particular draft. However, I often have no intention of sending an email straight away, and would rather just fire up my editor on the message file. So I wrote a simple script adding a new command to MH. The script works the same as other MH commands, taking as its argument the number of a mail message to edit. So now I can look at my drafts directory using the drafts command, then edit the message I want with 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
}
This is 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. Then, skipping the rest, we 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 because 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 separator, +5 says fourth field (we start counting with 0), and -n says sort numerically.
Next we grab either the last or the first line of this list -- the first or last draft email, respectively.
tail -1 OR head -1
Now we want to run the editor on it by passing the output of our long command to the argument list of the editor command, enclosing the whole thing in $(...).
The other command lines are much simpler. If we're given a numerical argument, we just build the pathname and hand it to vi directly.
A note on init files
Of course, we don't type in our aliases and functions anew each time we start up the shell. An interactive shell reads a number of init files on startup, and that's the best place to put our definitions.
What init file goes where depends on the shell you're using. In the beginning, there was the Bourne shell, /bin/sh. You won't find this 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
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; 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's invoked as. bash knows of three types of shell: two interactive, login and nonlogin, and one noninteractive.
For login shells, these rules apply:
/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. First 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.
~/.bashrc exists, it will be sourced.ENV is set, the file it points to is sourced, as if the commandif [ "$ENV" ]; then . $ENV; fi
had been executed. However, PATH is not used to search for the pathname given in ENV.
ksh can get a similar functionality to the bash .bash_logout file with the following trick. Put this line into .profile:trap '. $HOME/.sh_logout; exit' 0
When the shell exits, it will source the .sh_logout file. The same trick can also be used for bash when running as a nonlogin shell. Put the line into .bashrc.
Note that commands in these init files are sourced; that is, commands affect the current environment. Normally, executing a command doesn't affect the current, or parent, environment -- this is why shell scripts to cd to a directory don't affect the current directory of your login shell.
Although the Unix shell dates back 30 years to the era of teletypes and 300-baud modems, it remains a useful tool to this day. I hope this article has been helpful in showing you how to maximize it.
Unix Insider