DæmonNews: News and views for the BSD community

February 2002 Search Submit Article Contact Us Join Us Merchandise

Making friends with C-Shell and TC-Shell -- Part III

Konrad Heuer, <kheuer@gwdg.de>


Table of Contents

7. How to create your own commands
 7.1 Simple alias definitions
 7.2 Advanced alias definitions

8. Miscellaneous topics
 8.1 Environment variables
 8.2 Shell variables
 8.3 Shell programming
 8.4 Programmed completion
 8.5 Spelling correction
 8.6 Command substitution

9. Making yourself at home
 9.1 Shell execution modes
 9.2 Start-up files


Looking back

Part I published in the December 2001 issue of Daemon News gave a general introduction into history and tasks of Unix shells and dealt with command-line editing, command history, file name globbing and name completion in csh and tcsh.

Part II in the January 2002 issue discussed the following topics: Directory stack, input and output redirection, processes, jobs and job control.


7. How to create your own commands

7.1 Simple alias definitions

Both, csh and tcsh, allow the definition of alias commands, which can be very convenient in various situations:

For example, a frequently used command is ls with the option -l to create a long directory listing. It is very convenient to introduce for this the alias command ll:
% alias ll ls -l
% ll compile.log
-rw-r--r--  1 joe  nobody  66 Dec 17 13:10 compile.log

When analyzing the second command line the shell recognizes that ll has been defined as an alias, and replaces this expression internally with ls -l. Then the ls command gets executed.

An example of the second category is the ping utility, which is located in the directory /sbin. This directory contains tools mostly useful for the system administrator only, so a normal user would have no reason to include it in the search path. However, ping can be used to see if a remote computer is alive:

% alias ping /sbin/ping
% ping www.daemonnews.org
PING www.daemonnews.org (204.152.186.46): 56 data bytes
64 bytes from 204.152.186.46: icmp_seq=0 ttl=46 time=208.675 ms
64 bytes from 204.152.186.46: icmp_seq=1 ttl=45 time=214.535 ms
64 bytes from 204.152.186.46: icmp_seq=2 ttl=46 time=195.677 ms
^C
--- www.daemonnews.org ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 195.677/206.296/214.535/7.880 ms

The system queried answers correctly; the ping command is thus then terminated by pressing Control-C. The important thing here is that after ping is registered as an alias command using alias, it no longer has to be invoked with its full path name /sbin/ping.

If the alias command is entered without an argument, a list of all registered aliases is displayed:

% alias
ll      (ls -l)
ping    /sbin/ping

Some users prefer to make their working environment more fault tolerant:

% alias rm rm -i
% rm compile.log
remove compile.log? y

From now on, rm will always ask for a confirmation before removing files. To override the alias temporarily, one can enter:

% \rm compile.log

The file will be removed quietly since the \ character prevents the shell from alias substitution.

To permanently delete an alias, the unalias can be used:

% unalias rm

The alias command can also be invoked with only one argument; it then displays the corresponding alias definition:

% alias ll
ls -l

7.2 Advanced alias definitions

The alias command ll defined in the previous section can be improved by adding a pipeline to the more utility, in order to stop output after every full screen. The solution seems to be simple:

% alias lm 'ls -l | more'

The quotes are necessary to prevent the shell from interpreting the pipeline symbol immediately, instead of waiting for a later execution of the alias command. The invocation of lm for the working directory then works flawlessly:

% lm
total 18
-rwxr-xr-x  1 joe  nobody  4241 Dec 18 11:14 a.out
-rw-r--r--  1 joe  nobody    66 Dec 17 13:16 compile.err
-rw-r--r--  1 joe  nobody    66 Dec 17 13:10 compile.log
-rw-r--r--  1 joe  nobody     0 Dec 17 13:16 compile.out
-rw-r--r--  1 joe  nobody    36 Dec 17 15:40 simple.c
drwxr-xr-x  2 joe  nobody   512 Feb  2  1996 tb

However, if one tries to use the command lm with an argument, e.g. the name of a directory, difficulties emerge:

% lm tb
tb is a directory

What happens? The shell expands this command line to

% ls -l | more tb

which indeed makes no sense, since the argument gets placed in the wrong position, and more cannot display the contents of a directory. The correct command would be of course:

% ls -l tb | more

To solve the problem, the traditional access to the history buffer mentioned in section 2.3 of part I has to be used:

% alias lm 'ls -l \!* | more'
% lm tb
total 226
-rw-r--r--  1 joe  nobody    277 Nov 12 13:34 Makefile
-rw-r--r--  1 joe  nobody    433 Nov 12 13:34 ascii.bas
-rw-r--r--  1 joe  nobody  27928 Nov 12 13:34 bi.c
-rw-r--r--  1 joe  nobody   3409 Nov 12 13:34 ci.c
-rw-r--r--  1 joe  nobody   1835 Nov 12 13:34 help.c
-rw-r--r--  1 joe  nobody   1633 Nov 12 13:34 io.c
-rw-r--r--  1 joe  nobody    838 Nov 12 13:34 tb.c
-rw-r--r--  1 joe  nobody   3986 Nov 12 13:34 tb.h
-rw-r--r--  1 joe  nobody  48593 Nov 12 13:34 tb.ps
-rw-r--r--  1 joe  nobody  16270 Nov 12 13:34 tb.tex
-rw-r--r--  1 joe  nobody   6013 Nov 12 13:34 utils.c

The backslash character blocks the interpretation of !* at the time of alias definition. Later, when the alias command is invoked, this character string is replaced by the list of arguments actually entered. When evaluating alias commands containing history event specifications, the shell considers the command entered by the user as the last command.

Mostly all history event specifications can be used within alias definitions. For example,

% alias bak 'cp -p \!:1 \!:1.bak'

creates an alias command bak which makes a backup copy of the file specified as first argument:

% bak simple.c
% ls -l simple.c*
-rw-r--r--  1 joe  nobody  36 Dec 17 15:40 simple.c
-rw-r--r--  1 joe  nobody  36 Dec 17 15:40 simple.c.bak


8. Miscellaneous topics

8.1 Environment variables

A set of variables forms the environment of a process. These variables provide some information which may be useful for processes. The shell allows to list all environment variables as well as to change them or to add new ones. The complete environment of a parent process is passed to a child process on invocation. This means, each program started by the shell on a user's request, inherits a copy of the set of environment variables the shell owns. The command printenv gives a list of all environment variables:

% printenv
PATH=/bin:/usr/bin:/usr/X11R6/bin:/usr/local/bin:/usr/games:.
TERM=xterm
MAIL=/var/mail/joe
USER=joe
HOME=/usr/home/joe
SHELL=/bin/tcsh
HOSTTYPE=FreeBSD
VENDOR=intel
OSTYPE=FreeBSD
MACHTYPE=i386
SHLVL=1
PWD=/usr/home/joe
LOGNAME=joe
GROUP=nobody
HOST=hal9000

Worthwhile to remember are especially the variables PATH, the value of which defines the search path for executable files in the system, and SHELL containing the name of the shell to invoke whenever a utility program needs to start a shell.

To add further environment variables, the setenv command can be used:

% setenv EDITOR emacs

This variable tells all utilities which editor to start if necessary. Environment variables can be removed by using unsetenv:

% unsetenv EDITOR

To read out the value of an environment variable, use a leading dollar sign as for usual shell variables:

% echo $USER
joe

8.2 Shell variables

Shell variables have already been introduced in section 2.1. The shell allows the use of variables which can store data. These variables are different from environment variables, they are local to the shell and not passed to child processes. Various shell variables with pre-defined meanings have already been mentioned in many sections of this article series. The informational content of a variable can lie in whether it is set or not or in its value.

Variable names must consist of letters, digits, or the underscore character; the leading character has to be a letter, and the name may not be longer than twenty characters. Variables can be of scalar or array type and may take numerical or string values:

% set three = 3
% set vowels = ( a e i o u )
% echo $three
3
% echo $vowels[2]
e

It is possible to test whether a shell (or environment) variable has been set or not by using the prefix $?:

% echo $?three
1
% echo $?two
0

As can be seen from the example, if the variable is not set, the operation will return zero.

The values of some special shell and environment variables are kept identical by the shell. The variables in question are group and GROUP, home and HOME, path and PATH, shlvl and SHLVL, term and TERM and user and USER.

Some useful shell variables which are not mentioned anywhere else in the article series are:

8.3 Shell programming

Both, csh and tcsh, offer a set of internal (built-in) commands which allow to write shell scripts looking (in a way) similar to C code. That is the reason why csh is called C shell. By the way, the T in tcsh is derived from the name of the TENEX operating system the user interface of which already provided useful command completion about 25 years ago. This impressed and influenced the author of tcsh.

A famous article csh programming considered harmful has been posted to some newsgroups years ago, and indeed, csh has had a lot of bugs and disadvantages.

In the meantime, bugs have been fixed, and tcsh behaves much better. Nevertheless, both shells should at most be used for small scripts only. The reason simply is that even tcsh is weak in advanced i/o redirection which is often needed in shell programming.

However, interactive use is a different matter, and especially tcsh is quite strong here. So, this article has a focus on interactive use.

One instruction useful not only in scripts but often also when entering commands interactively is foreach to process a sequence of arguments; e.g., to print some manual pages on a PostScript printer:

% foreach file ( /usr/share/man/man1/{csh,gcc,tcsh}.1.gz )
foreach? echo $file
foreach? gunzip < $file | groff -man | lpr -Pps
foreach? end
/usr/share/man/man1/csh.1.gz
/usr/share/man/man1/gcc.1.gz
/usr/share/man/man1/tcsh.1.gz

8.4 Programmed completion

Name completion in csh and tcsh has been introduced in section 3.2. Programmed name completion is an additional feature tcsh offers and should not be confused with script programming.

Two simple examples will illustrate programmed completion. When trying to complete an argument of the commands cd or rmdir, the shell will by default offer plain files as possible completions, too. To change this, enter:

% complete cd 'p/1/d/'
% complete rmdir 'p/*/d/'

From now on, for cd and rmdir, when completing a word in a given position (letter p), complete the first word (digit 1) or all words (character *) with a directory name (letter d) only.

The second example will modify the behavior of the shell when requesting completion while starting ftp sessions:

% complete ftp 'p/1/(ftp.freebsd.org ftp.netbsd.org ftp.openbsd.org)/'
% ftp ftp.f<TAB>reebsd.org

8.5 Spelling correction

Another additional feature of tcsh is spelling correction, a convenient way to handle typos. After assigning one of the values cmd or all to the shell variable correct, tcsh will offer some aid:

% set correct = cmd
% moer simple.c
 
CORRECT>more simple.c (y|n|e|a)? yes
#include 
main(){for(;;);}

As shown in the example, the correction can be accepted (y), or otherwise rejected (n), or the command can be edited (e) or aborted (a).

After setting correct to all, tcsh will offer correction not only for command names, but for arguments, too. This can be nerving and dangerous if entered commands create new files since the correction mechanism will treat names of non-existing files as typos.

8.6 Command substitution

Command substitution is a way to use data written by utility programs to the standard output channel directly in the shell command line. The date utility may serve as an example; it can be used to print the current time to stdout:

% date '+%H:%M:%S'
11:17:58

Now, one can use this kind of output to construct file names:

% cc simple.c >& compile.log.`date '+%H:%M:%S'`
% ls -l compile.log.*
-rw-r--r--  1 joe  nobody  0 Jan 15 11:23 compile.log.11:23:11

Any expression enclosed in backquotes is regarded as a command, executed, and replaced by the resulting output (newlines are replaced by spaces). It is possible to use history event specifications within backquotes.


9. Making yourself at home

9.1 Shell execution modes

Three modes of execution of a shell have to be distinguished:

  1. A shell executing a script (a sequence of commands stored in a file or read from redirected stdin) is non-interactive.
  2. A shell accepting commands from the keyboard is interactive.
  3. A shell immediately invoked within the context of a user's login procedure is an interactive login shell.

9.2 Start-up files

Common (system-wide) shell start-up files can be used to set up a convenient environment for all users on the system, and personal start-up files allow to create an individual working environment. Furthermore, there are «shutdown» files too, getting executed during the process of logging out. Among others, typical tasks of start-up files are to set environment and/or shell variables and to define alias commands.

Table 5: Start-up and «shutdown» files used by csh and tcsh
File nameUse
/etc/csh.cshrc Common start-up file read by all shells
/etc/csh.login Common start-up file read by login shells
/etc/csh.logout Common shutdown file read by login shells
~/.cshrc Personal start-up file read by all shells (csh)
~/.tcshrc Personal start-up file read by all shells (tcsh)
~/.login Personal start-up file read by login shells
~/.logout Personal shutdown file read by login shells

On invocation of a new shell, the common start-up files are read first. Thus a user can override the system-wide settings by his or her personal ones since the personal files are read subsequently. Login shells read /etc/csh.login after /etc/csh.cshrc and ~/.login after ~/.cshrc. If tcsh does not find ~/.tcshrc, it will then read ~/.cshrc. Furthermore, tcsh can be compiled in such a way that the two login files are read prior to the cshrc files (the version shell variable will then contain the option string lf).

Commands that need to be executed only once during a session should be placed in one of the login files. Typical examples are commands that affect environment variables. Instructions setting shell variables or defining aliases have to be included in the cshrc files.

By the way, don't get confused by the file /.cshrc in the root directory/. It is read by single-user shells in single-user mode only. In normal multi-user operation of the system, the file is disregarded. Modifications to /.cshrc require a lot of care since you may lock yourself out completely in single-user mode!

In general, after changing one of the start-up files, do not log out before you have been able to log in successfully on a different virtual or pseudo-terminal or in a different window. This is the best way to test and to repair the files if necessary.

Non-interactive shells read the cshrc files, too; but this can be suppressed by the command line option -f on invocation.

Here is an example for /etc/csh.login or ~/.login which picks up topics covered in the article series; the character # can be used to comment within these files:

# /etc/csh.login or ~/.login

# set search path for executable files
set path = ( /bin /usr/bin /usr/X11R6/bin /usr/local/bin /usr/games . )
if ( "$user" == root ) set path = ( /sbin /usr/sbin /usr/local/sbin $path )

# set the default editor
setenv EDITOR emacs

# background jobs will be suspended if they try to write to stdout/stderr
stty tostop

# print a random adage
fortune

# allow written messages from other users
mesg y

# everyone can read and execute newly created files
umask 022

The next example shows a possible file /etc/csh.cshrc or ~/.cshrc:

# /etc/csh.cshrc or ~/.cshrc

# settings will be done only for interactive shells
if ( $?prompt ) then

# set useful csh variables
  set filec
  set ignoreeof
  set history = 250
  set noclobber
  set prompt = "${user}@`hostname -s`% "
  set savehist

# set useful additional or modified tcsh variables
  if ( "$shell:t" == tcsh ) then
    set autolist
    set correct = cmd
    set prompt = '%n@%m[%~]%% '
    set rmstar
    set savehist = ( 250 merge )
  endif

# set useful csh aliases
  alias ping /sbin/ping
  alias pd   pushd
  alias lf   ls -F
  alias ll   ls -l
  alias lm   'ls -l \!* | more'

# set useful tcsh aliases and programmed completions
  if ( "$shell:t" == tcsh ) then
    alias lf ls-F  # tcsh built-in with faster execution
    complete {cd,pd} 'p/1/d/'
    complete rmdir 'p/*/d/'
  endif

# set different root shell prompt
  if ( "$user" == root ) set prompt = "`hostname -s`# "

endif


Closing words

Now we are at the end of this article series. Not all features of csh and tcsh could be mentioned in depth. But what did we learn? It was my intention to show that csh and especially tcsh are absolutely useful when interactively used by system administrators and ordinary users. Linux was soon tied down to bash since this is the GNU shell. But BSD systems come with a true Bourne shell able to run scripts and - with csh or tcsh. Use bash if you like - but it is not a law of nature to do so on a BSD system!


Further reading

[1] tcsh man page; command: man tcsh
[2] Paul DuBois: Using csh&tcsh, O'Reilly 1995, ISBN 1-56592-132-1



Author maintains all copyrights on this article.
Images and layout Copyright © 1998-2001 Dæmon News. All Rights Reserved.