Let's dig in and find out some of the key additional capabilities of the most popular UNIX user shells.
If you went through school in the United States, you doubtless have heard the aphorism “Those who do not study history are doomed to repeat it.” UNIX stands this concept on its head. For UNIX, the aphorism is best stated “Those who are aware of their history can easily repeat it.” |
Both the C shell and the Korn shell build a table of commands as you enter them and assign them each a command number. Each time you log in, the first command you enter is command 1, and the command number is incremented for each subsequent command you enter. You can review or repeat any previous command easily with just a few keystrokes.
To review your history in the C shell, enter history at the csh prompt. Odds are that nothing will happen, though, because by default csh remembers only the very last command. To have it begin building a list of commands, you must turn on this feature through the environment setting set history=n, where n is the number of commands you'd like it to recall.
By contrast, ksh has a default history list size of 128 commands, plenty for anyone. To review your history in ksh, you also can use the history command. Actually, though, it's an alias, and the real command is the more cryptic fc -l. In Korn shell, you don't need to make any changes; the history mechanism is ready to use immediately.
Log in to your system so that you have a C shell prompt. If you're currently in the Bourne shell or a shell other than C shell, this would be a great time to use chsh to change shells.
% history
%
The shell indicates that it has no history. Sir Winston Churchill doubtless would shake his head and mutter under his breath, “To have become such a sophisticated operating system yet never to have studied history!”
I need to turn on the shell history mechanism, so I will enter the following command:
% set history=100
Still there is no feedback, but I can check the status of all the shell parameters by entering set at the csh prompt:
% set
argv ()
cwd /users/taylor
filec
history 100
home /users/taylor
host limbo
noclobber
path (. /users/taylor/bin /bin /usr/bin /usr/ucb /usr/local /etc
/usr/etc /usr/local/bin /usr/unsup/bin
savehist 50
shell /bin/csh
status 0
system limbo
term unknown
user taylor
To take a break, I use w to see who is logged on and what they're doing, use ls to check my files again, and use date to see whether my watch is working:
% w | head 11:41am up 17:59, 103 users, load average: 0.54, 0.53, 0.49 User tty login@ idle JCPU PCPU what root console 6:02pm 12:13 1 1 -csh taylor ttyAf 11:40am 5 2 w bev ttyAg 9:25am 14 1:09 3 -csh rekunkel ttyAh 11:37am 4 3 rlogin ccn gabh ttyAi 10:41am 6 46 16 talk dorits af5 ttyAj 8:27am 21 33 1 -ksh techman ttyAk 9:47am 25 7 gopher tuccie ttyAl 11:37am 1 1 mail Broken pipe % ls Archives/ OWL/ buckaroo.confused sample InfoWorld/ awkscript dickens.note sample2 Mail/ bin/ keylime.pie src/ News/ buckaroo owl.c temp/ % date Tue Dec 8 11:41:29 EST 1998
Notice that at the end of the w command output, the system noted Broken pipe. This is nothing to be anxious about; it's just an indication that there was much more in the pipeline when the program quit. You can see that head read only the first 10 lines. The first line of the w output shows that there are 103 users on the system, which means that head ignored 94 lines of output. Unlike real plumbing, fortunately, this broken pipe doesn't allow the spare data to spill onto the basement floor! |
Now, when I enter history, the shell remembers the previous commands, presenting them all in a neat, numbered list:
% history
1 set history=100
2 w | head
3 ls
4 date
5 history
To turn this on permanently, add the set history command to your .cshrc. If you want the shell to remember commands even if you log out and log back in, also specify the setting savehist. I choose to do this by entering vi +$ .cshrc and adding the following line:
set noclobber system=limbo filec
umask 007
setprompt
endif
set history=100 savehist=50 _
~
~
If you glance back at the output of the set command, you can see that I already have both of these parameters set: 100 commands will remain in the history list while I'm working, and 50 commands will be retained for the next time I log in. What's particularly helpful is that any time I specify a number n for either history list, the shell actually saves the most recent n commands, so I have the most recent 100 commands for review while I'm using the system, and the 50 most recent commands remembered when I log in later.
Make this change to your .cshrc file, log out, and log in again to ensure that your history mechanism is set up correctly.
As with much of UNIX, turning on the history mechanism of the C shell is quite easy once you learn the trick. In this case, simply remember that you need to specify a set history value to have the shell begin remembering what's going on with your interaction. In Korn shell, you don't need to make any changes; it's ready to use immediately. |
There are three main mechanisms for working with the history list. You can specify a previous command by its command number, by the first word of the command, or, if you're working with the most recently executed command, by a special notation that easily fixes any mistakes you might have made as you typed it. |
Every history command begins with an exclamation point. If the 33rd command you entered was the who command, for example, you can execute it by referring to its command number: Enter !33 at the command line. You can execute it also by entering one or more characters of the command: !w, !wh, or !who. You must enter enough characters to uniquely identify it in the history list.
To edit a previous command, type a caret, the pattern you want to change, another caret, and the correct pattern. If you just entered awk -F, '{print $2}' and realize that you meant to type a colon, not a comma, as the field delimiter, ^,^: will do the trick.
A very useful shorthand is !!, which repeats the most recently executed command. Two other history references are valuable to know: !$ expands to the last word of the preceding line (which makes sense because $ always refers to the end of something, whether it be a line, the file, or, in this case, a command), and !* expands to all the words in the preceding command except the first word. So, for example, if I entered the command ls /usr /etc /dev and then immediately entered the command echo !*, the second command would be expanded automatically to echo /usr /etc /dev.
Korn shell offers all of this and more. You can repeat commands by number by specifying rn, where n is the command number (for example, r33). You can also repeat by name with rname, as in rwho to repeat the most recent who command. Much more useful is the ksh capability to directly edit a command with the familiar vi or emacs command keys, without leaving the command line. Without any arguments, r will repeat the preceding command.
First, I need to spend a few minutes building up a history list by running various commands:
% w | head 11:58am up 18:14, 81 users, load average: 0.54, 0.44, 0.38 User tty login@ idle JCPU PCPU what root console 6:02pm 12:30 1 1 -csh hopkins ttyAe 11:49am 4 4 telnet whip.isca.uiowa.edu taylor ttyAf 11:40am 8 2 wbev ttyAg 9:25am 31 1:09 3 -csh af5 ttyAj 8:27am 37 33 1 -ksh techman ttyAk 9:47am 4 1:11 4 elm tuccie ttyAl 11:37am 2 1 mail trice ttyAm 8:16am 1:21 5 2 -csh Broken pipe % date Tue Dec 8 11:58:19 EST 1998 % ls Archives/ OWL/ buckaroo.confused sample InfoWorld/ awkscript dickens.note sample2 Mail/ bin/ keylime.pie src/ News/ buckaroo owl.c temp/ % cat buckaroo I found myself stealing a peek at my own watch and overhead General Catbird's aide give him the latest. "He's not even here," went the conversation. "Who?" "Banzai." "Where the hell is he?" "At the hospital in El Paso." "What? Why weren't we informed? What's wrong with him?" %
Now I will check my history list to see what commands are squirreled away for later:
% history
51 set history=100
52 history
53 w | head
54 date
55 ls
56 cat buckaroo
57 history
I already have my history mechanism turned on, so my commands begin numbering with 51 rather than with 1. Your system might be different. Regardless of what the command numbers are, they'll work! |
To repeat the date command, I can specify its command number:
% !54
date
Tue Dec 8 12:04:08 EST 1998
Notice that the shell shows the command I've entered as command number 54. The ksh equivalent here would be r 54.
A second way to accomplish this repeat, a way that is much easier, is to specify the first letter of the command:
% !w
w | head
12:05pm up 18:23, 87 users, load average: 0.40, 0.39, 0.33
User tty login@ idle JCPU PCPU what
root console 6:02pm 12:37 1 1 -csh
lloyds ttyAb 12:05pm 1 1 mail windberg
lusk ttyAc 12:03pm 3 2 gopher
hopkins ttyAe 11:49am 8 8 telnet whip.isca.uiowa.edu
taylor ttyAf 11:40am 1 14 3 w
bev ttyAg 9:25am 38 1:09 3 -csh
libphar ttyAh 12:03pm 3 3 elm
dgrove ttyAi 12:02pm 5 2 more inbox/16
Broken pipe
Now glance at the history list:
% history
51 set history=100
52 history
53 w | head
54 date
55 ls
56 cat buckaroo
57 history
58 date
59 w | head
60 history
Commands expanded by the history mechanism are stored as the expanded command, not as the history command that actually was entered. Thus, this is an exception to the earlier rule that the history mechanism always shows what was previously entered. It's an eminently helpful exception!
History commands are quite helpful for people working on a software program. The most common cycle for programmers to repeat is edit-compile-run, over and over again. The commands UNIX programmers use most often probably will look something like vi test.c, cc -o test test.c, and test, to edit, compile, and run the program, respectively. Using the C shell history mechanism, a programmer easily can enter !v to edit the file, !c to compile it, and then !t to test it. As your commands become longer and more complex, this function proves more and more helpful.
It's time to experiment a bit with file wildcards:
% ls
Archives awkscript dickens.note src
InfoWorld bin keylime.pie temp
Mail buckaroo owl.c
News buckaroo.confused sample
OWL cshrc sample2x
Oops! I meant to specify the -F flag to ls. I can use !! to repeat the command; then I can add the flag:
% !! -F
ls -F
Archives/ awkscript dickens.note src/
InfoWorld/ bin/ keylime.pie temp/
Mail/ buckaroo owl.c
News/ buckaroo.confused sample
OWL/ cshrc sample2
I want to figure out a pattern or two that will let me specify both buckaroo files, the dickens file, and sample2, but not sample. This is a fine example of where the echo command can be helpful:
% echo b* d* s*
bin buckaroo buckaroo.confused dickens.note sample sample2 src
That's not quite it. I'll try again:
% echo bu* d* sa*
buckaroo buckaroo.confused dickens.note sample sample2
That's closer. Now I just need to remove the sample file:
% echo bu* d* sa*2
buckaroo buckaroo.confused dickens.note sample2
That's it. Now I want to compute the number of lines in each of these files. If I use the csh history mechanism, I can avoid having to enter the filenames again:
% wc -l !*
wc -l bu* d* sa*2
36 buckaroo
11 buckaroo.confused
28 dickens.note
4 sample2
79 total
Notice that the !* expanded to the entire preceding command except the very first word.
The general idea of all these history mechanisms is that you specify a pattern that is replaced by the appropriate command in the history list. So you could enter echo !! to have the system echo the last command, and it would end up echoing twice. Try it. |
Korn shell users will find that echo !! produces !! and that the ksh repeat-last-command of r also will fail. If your last command was echo r, the result will be r. Alas, there is no analogous shorthand to the convenient !! -F in csh. On the other hand, if FCEDIT is set to vi or EMACS, you can pop into the editor to change the command by typing fc. |
What happens if I use !$ instead?
% wc -l !$
wc -l sa*2
4 sample2
In the middle of doing all this, I became curious about how many people on my system have first names that are four letters long. Is this impossible to compute? Not with UNIX!
The first step is to extract the full names from the /etc/passwd file:
% awk -F: '{ print $5 }'
The system does not respond. I forgot to specify the filename!
% !! < /etc/passwd
awk -F: '{print $5}' < /etc/passwd
limbo root,,,,
USENET News,,,,
INGRES Manager,,,,
(1000 user system) DO NOT,,,,
Vanilla Account,,,,
The Ferryman,,,,
I can use ^c to stop this output, because I've seen enough to know that it's what I want. Next, I use awk again to pull just the first names out of this list:
% !! | awk '{print $1}' awk -F: '{print $5}' < /etc/passwd | awk '{print $1}' root USENET INGRES (1000 Vanilla The Account ^c %
It looks okay. Now the final step: I need to revise this awk script to look at the length of each name and output the name only if it's four letters long:
% !-2 | awk '{ if (lng($1) == 4) print $0 }'
awk -F: '{print $5} ' < /etc/passwd | awk '{ if (lng($1) == 4) print $0 }'
I got no output at all! The reason is that I mistyped length as lng. Fortunately, to fix this is simplicity itself with C shell history commands. Remember, the format is ^old^new:
% ^lng^length awk -F: '{print $5}' < /etc/passwd | awk '{ if (length($1) == 4) print $0 }' ,,,, Paul Town,,,, Pete Cheese,,,, John Smith,,,, Dana Tott,,,, Dick Ply,,,, Mike Moliak,,,, Bill Born,,,, Dale Tott,,,, Bill Rison,,,, Gary Flint,,,, Doug Sherwood,,,, Ruth Raffy,,,, Dave Sean,,,, ^c %
That's very close. I just need to pipe the output of this command to wc:
% !! | wc -l
awk -F: '{print $5} ' < /etc/passwd | awk '{ if (length($1) == 4) print
$0 }' | wc -l
723
If you are using Korn shell, here's where it shines! Make sure that the environment variable EDITOR is set to your preferred editor:
$ echo $EDITOR
vi
$
Now, any time you're entering a command, you can press the Escape key and be in ksh history-edit command mode. The usual vi commands work, including h and l to move left and right; i and Escape to enter and leave insert mode; w, W, b, and B to zip about by words; and 0 and $ to move to the beginning or end of the line.
Much more useful are k and j, which replace the current command with the preceding or next, enabling you to zip through the history list.
If I'd just entered who and then ls, to append | wc -l to the who command, I could press the Escape key:
$_.
Now each time I type k, I will see the preceding command. Typing k once reveals this:
$ls
Typing k a second time reveals this:
$who
That's the right command, so $ moves the cursor to the end of the line:
$who
Typing a appends, at which point I can add | wc -l like this:
$who | wc -l
Pressing Return results in ksh actually executing the command:
$ who | wc -l 130 $_
The history mechanisms of the shells are wonderful time savers when you're working with files. I find myself using the csh !! and !word mechanisms daily either to build up complex commands (such as the preceding example, in which I built up a very complex command, step by step) or to repeat the most recently used edit commands. Table 14.1 summarizes the available csh history mechanisms. I encourage you to learn and use them. They soon will become second nature and will save you lots of typing. |
Command | Function |
!! | Repeat the preceding command. |
!$ | Repeat the last word of the preceding command. |
!* | Repeat all but the first word of the preceding command. |
^a^b | Replace a with b in the preceding command. |
!n | Repeat command n from the history list. |
If you think the history mechanism has the potential to save you typing, you'll be glad to learn about the command-alias mechanism in the Korn and C shells. Using aliases, you can easily define new commands that do whatever you'd like, or even redefine existing commands to work differently, have different default flags, or more! |
The general format for using the alias mechanism in csh is alias word command-sequence, and in ksh it is alias word=commands. If you enter alias without any specified words, the output shows a list of aliases you have defined. If you enter alias word in csh, the output lists the current alias, if there is one, for the specified word.
One of the most helpful aliases you can create specifies certain flags to ls so that each time you enter ls, the output will look as though you used the flags with the command. I like to have the -FC flags set.
% ls
Archives awkscript dickens.note src
InfoWorld bin keylime.pie temp
Mail buckaroo owl.c
News buckaroo.confused sample
OWL cshrc sample2
Now I'll create a C shell alias and try it again:
% alias ls 'ls -CF' % ls Archives/ awkscript dickens.note src/ InfoWorld/ bin/ keylime.pie temp/ Mail/ buckaroo owl.c News/ buckaroo.confused sample OWL/ cshrc sample2
This is very helpful!
The ksh equivalent would be alias ls = 'ls -CF'.
If you're coming from the DOS world, you might have found some of the UNIX file commands confusing. In DOS, for example, you use DIR to list directories, REN to rename files, COPY to copy them, and so on. With aliases, you can re-create all those commands, mapping them to specific UNIX equivalents:
% alias DIR 'ls -lF' % alias REN 'mv' % alias COPY 'cp -i' % alias DEL 'rm -i' % DIR total 33 drwx------ 2 taylor 512 Nov 21 10:39 Archives/ drwx------ 3 taylor 512 Dec 3 02:03 InfoWorld/ drwx------ 2 taylor 1024 Dec 3 01:43 Mail/ drwx------ 2 taylor 512 Oct 6 09:36 News/ drwx------ 4 taylor 532 Dec 6 18:31 OWL/ -rw-rw---- 1 taylor 126 Dec 3 16:34 awkscript drwx------ 2 taylor 512 Oct 13 10:45 bin/ -rw-rw---- 1 taylor 1393 Dec 5 18:48 buckaroo -rw-rw---- 1 taylor 458 Dec 4 23:22 buckaroo.confused -rw------- 1 taylor 1339 Dec 2 10:30 cshrc -rw-rw---- 1 taylor 1123 Dec 5 18:16 dickens.note -rw-rw---- 1 taylor 12556 Nov 16 09:49 keylime.pie -rw-rw---- 1 taylor 8729 Dec 2 21:19 owl.c -rw-rw---- 1 taylor 199 Dec 3 16:11 sample -rw-rw---- 1 taylor 207 Dec 3 16:11 sample2 drwx------ 2 taylor 512 Oct 13 10:45 src/ drwxrwx--- 2 taylor 512 Nov 8 22:20 temp/ % COPY sample newsample %
To see what aliases have been defined, use the alias command:
% alias
COPY cp -i
DEL rm -i
DIR ls -lF
REN mv
ls ls -CF
You could improve the alias for DIR by having the output of ls fed directly into the more program so that a directory listing with a lot of output will automatically pause at the end of each page. To redefine an alias, just define it again:
% alias DIR 'ls -lF | more'
To confirm that the alias is set as you desire, try this:
% alias DIR
DIR ls -lF | more
If you're defining just one command with an alias, you don't really need to use the quotation marks around the command argument. But what would happen if you entered alias DIR ls -lF | more? The alias would be set to ls -lF, and the output of the alias command would be fed to the more program, which is quite different from what you desired. Therefore, it's just good form to use the quotation marks and a good habit to get into. |
Aliases are a great addition to any command shell, and with the arcane UNIX commands, they also can be used to define full-word commands as synonyms. For example, if you decide you'd like the simplicity of remembering only the command move to move a file somewhere else, you could add the new alias alias move mv to your .cshrc file if you're using C shell or alias move=mv to your .profile if you prefer Korn shell, and the shell would include a new command. |
Because I have used the C shell for many years, I have created various aliases to help me work efficiently. A few of the best are shown in this section. |
To see what aliases I have defined, I can use the same command I used earlier:
% alias
cd chdir !* ; setprompt
diff /usr/bin/diff -c -w
env printenv
from frm -n
info ssinfo
ls /bin/ls -F
mail Mail
mailq /usr/lib/sendmail -bp
netcom echo Netcom login: taylor;rlogin netcom.com
newaliases echo you mean newalias...
rd readmsg $ | page
rn /usr/local/bin/rn -d$HOME -L -M -m -e -S -/
setprompt set prompt="$system ($cwd:t) ! : "
ssinfo echo "connecting..." ; rlogin oasis
sunworld echo SunWorld login: taylor;rlogin sunworld.com
Recall that each of these aliases started out in my .cshrc file surrounded by single quotation marks:
% grep alias .cshrc
alias diff '/usr/bin/diff -c -w'
alias from 'frm -n'
alias ll 'ls -l'
alias ls '/bin/ls -F'
alias mail Mail
alias mailq '/usr/lib/sendmail -bp'
alias netcom 'echo Netcom login: taylor;rlogin netcom.com'
alias sunworld 'echo SunWorld login: taylor;rlogin sunworld.com'
alias newaliases 'echo you mean newalias...'
alias rd 'readmsg $ | page'
alias rn '/usr/local/bin/rn -d$HOME -L -M -m -e -S -/'
alias cd 'chdir !* ; setprompt'
alias env 'printenv'
alias setprompt 'set prompt="$system ($cwd:t) ! : "'
# special aliases:
alias info ssinfo
alias ssinfo 'echo "connecting..." ; rlogin oasis'
Also notice that the shell always keeps an alphabetically sorted list of aliases, regardless of the order in which they were defined.
Most of these aliases are easy to understand. For example, the first alias, diff, ensures that the command diff always has the default flags -c and -w. If I enter from, I want the system to invoke frm -n; if I enter ll, I want the system to invoke ls -l; and so on.
Some commands can cause trouble if entered, so creating an alias for each of those commands is a good way to stay out of trouble. For example, I have an alias for newaliases; if I accidentally enter that command, the system gently reminds me that I probably meant to use the newalias command:
% newaliases
you mean newalias...
I have created aliases for connecting to accounts on other systems. I like to name each alias after the system to which I'm connecting (for example, netcom, sunworld):
% alias netcom echo Netcom login: taylor;rlogin netcom.com % alias sunworld echo SunWorld login: taylor;rlogin sunworld.com
Separating commands with a semicolon is the UNIX way of having multiple commands on a single line, so when I enter the alias netcom, for example, it's as if I'd entered all these commands one after another:
echo Netcom login: taylor rlogin netcom.com
You can't enter alias netcom sunworld to list the netcom and sunworld aliases because that command means to replace the alias for netcom with the command sunworld. |
Two aliases worth examining more closely are those for the cd and setprompt commands. As you learn in a few moments, you can set your shell prompt to be just about any characters you'd like. (Hang on for just a paragraph or two, and you will learn all about what's occurring in the next example!) I like to have my prompt indicate where in the file system I'm currently working. To ensure that the prompt is always up-to-date, I simply alias the cd command so that each time I change directories, the prompt is recalculated:
% alias cd
chdir !* ; setprompt
% alias setprompt
set prompt="$system ($cwd:t) ! : "
The chdir command does the same thing as cd and is intended for use within aliases as shown. So if you find chdir easier to remember than cd, you can use it instead. |
Aliases are what makes both the C shell and Korn shell such great command interfaces. I can, and do, easily customize the set of commands and the default flags (look at all the options I set as default values for the rn command). I even turn off some commands that I don't want to enter accidentally. Let your imagination run wild with aliases. If you decide you really like one and you're using csh, add the alias to your .cshrc so that it's permanent (.profile if you're using ksh). If you want to turn off an alias, you can use the unalias command, and it's gone until you log in again. For example, unalias netcom would temporarily remove from the shell the netcom alias shown earlier. |
Up to this point, the command prompt I've seen is a boring %. It turns out that the C shell lets you set your prompt to just about any possible value, with set prompt="value". |
The Korn shell equivalent is even easier: PS1="value". Note that PS1 must be all uppercase for this to work.
I'm getting tired of UNIX being so inhospitable. Fortunately, I easily can change how it responds to me:
% set prompt="Yes, master? "
Yes, master?
That's more fun!
The ksh equivalent is PS1="Yes, master? "
There are a lot of things you can tuck away in your prompt that can be of great help. The first useful variable is cwd, which holds the current working directory:
Yes, master? set prompt="In $cwd, oh master: "
In /users/taylor, oh master:
What happens if I change directories?
In /users/taylor, oh master: cd / In /users/taylor, oh master: pwd / In /users/taylor, oh master:
This is not so good. Now you can see why it's necessary to alias cd to maintain the prompt.
Some special ! values can be added to the prompt definition, as shown in Table 14.2.
Here are a few examples of other C shell prompts and what happens when you use them:
In /, oh master: set prompt="(!) % "
(132) %
The ksh equivalent is PS1="(!) $ ".
The number in parentheses is the command number, as used by the C shell history mechanism:
(132) % echo hi hi (133) % ls News mailing.lists.usenet usenet.1 usenet.alt (134) % !132 echo hi hi (135) %
Every time I log in, I automatically set the variable system to the name of the current computer:
(135) % set prompt="$system (!) % "
limbo (136) %
I like to include in my prompt the basename of the current directory, as shown in the following example. Basename means the closest directory name, so the basename of /usr/home/taylor is taylor, for example. Also, I replace the percent sign with a colon, which is a bit easier to read. There is a slight problem, however; having : instead of % means that I have to remember that I'm in C shell (or Korn shell, as the case may be).
limbo (136) % set prompt="$system ($cwd:t) ! : "
limbo (taylor) 137 :
Value | Expands to |
`cmd` | The results of executing cmd. |
! | The current command number. |
$var | The value of var. |
$var:t | The tail (last word) of the value of var. |
Now I glance back at the aliases for setprompt and cd, with all these things in mind:
limbo (taylor) 139 : alias cd chdir !* ; setprompt limbo (taylor) 140 : alias setprompt set prompt="$system ($cwd:t) ! : " limbo (taylor) 141 :
You can see that the setprompt alias defines the C shell prompt as $system ($cwd:t) ! : ", although the actual line in the .cshrc file includes the backslash (as expected):
limbo (taylor) 141 : grep prompt= .cshrc
alias setprompt 'set prompt="$system ($cwd:t) ! : "'
limbo (taylor) 142 :
Each time I change directories, I use the combined commands of the cd alias (chdir !*) to change the current directory, and then I use setprompt to compute the new prompt.
The !* notation in a shell alias expands to all the words you specify after the alias word on the command line. For example, if you have an alias for dir that is "echo !* ; ls !*", entering dir /home actually executes echo /home followed by ls /home. |
Experiment and find a set of variables that can help you customize your UNIX prompt. I strongly recommend that you use command numbers to familiarize yourself with the history mechanism. |
The command-alias capability is a helpful way to cut down on entering short commands time and again, but what if you have a series of 5 or 10 commands you often enter in sequence? That's where shell scripts can help. At their simplest, shell scripts are a series of shell commands that appear in a file in exactly the order in which they'll be entered. If you change the permissions of the file to add execute permission, you can enter the name of the file as if it were just another UNIX command. |
Let's move into my own bin directory and consider a script or two I have there:
limbo (ucb) 42 : cd limbo (taylor) 43 : cd bin limbo (bin) 44 : file * bounce.msg: executable shell script calc: SYMMETRY i386 executable (0 @ 0) not stripped version 1 fixit: SYMMETRY i386 executable (0 @ 0) not stripped version 1 massage: SYMMETRY i386 executable (0 @ 0) not stripped version 1 punt: shell script rumor.mill.sh: shell script say.hi: ascii text limbo (bin) 45 : cat -n punt 1 : Use /bin/sh 2 3 # Punt: punt a news article from within "rn" to yourself. 4 5 trap "/bin/rm -f /tmp/punt.$$" 0 1 9 15 6 7 [email protected] 8 9 cat - > /tmp/punt.$$ 10 11 if [ "$1" != "" ] ; then 12 ADDRESS=$1 13 else 14 ADDRESS=$SENDTO 15 fi 16 17 /usr/lib/sendmail $ADDRESS < /tmp/punt.$$ 18 19 echo Punted a `wc -l </tmp/punt.$$` line news article to $ADDRESS 20 21 exit 0 22
This script is intended to be part of a pipeline, and it will send a copy of the stream of information either to the default address (SENDTO) or to a specified person ($1 is the first argument given to the script in this case). As shown earlier in the discussion of system prompts, any text that appears in backquotes is interpreted as a command and is executed, and the results of that command are added in its place in the subsequent command. In this case, the echo command on line 19 computes the number of lines in the specified file, and that number is then included in the output, which typically looks like this: Punted a 17 line news article.
Notice that the first character of this file is a colon. It turns out that the C shell interprets scripts only if the first character of the script is a #. Otherwise, it lets the Bourne shell (sh) run the commands, as in this case. |
That's all well and interesting, but I want to create a new shell script. The first step is to make sure that I'm creating the script in a directory that is included in my search path (otherwise, I won't be able to use the script as a command):
limbo (bin) 46 : pwd /users/taylor/bin limbo (bin) 47 : echo $PATH .:/users/taylor/bin:/bin:/usr/bin:/usr/ucb:/usr/local:/etc:/usr/etc: /usr/local/bin:/usr/unsup/bin (bin) 48 :
Here's a simple shell script that shows how shell scripts can be of assistance:
limbo (bin) 86 : cat new.script
# sample shell script
echo searching for shell scripts
pwd
echo " "
file * | grep script | sed 's/:/ /' | awk '{print $1}'
exit 0
This script lists the names of all files in the current directory that it identifies as shell scripts:
limbo (bin) 88 : chmod +x new.script limbo (bin) 89 : new.script searching for shell scripts /users/taylor/bin bounce.msg locate new.script punt rumor.mill.sh
To confirm that the new command works, look at what file reports about this same directory:
limbo (bin) 90 : file *
bounce.msg: executable shell script
calc: SYMMETRY i386 executable (0 @ 0) not stripped version 1
fixit: SYMMETRY i386 executable (0 @ 0) not stripped version 1
locate: shell script
massage: SYMMETRY i386 executable (0 @ 0) not stripped version 1
new.script: commands text
punt: shell script
rumor.mill.sh: shell script
say.hi: ascii text
limbo (bin) 91 :
A more interesting script is one that can search through all the directories in my PATH, looking for any occurrences of a specified filename:
limbo (bin) 92 : cat locate
# locate - find copies of a file
#
# this should be run by the C shell
set name=$1
foreach directory (`echo $PATH | sed 's/:/ /g'`)
if ( -f $directory/$name) then
ls -l $directory/$name
endif
end
The foreach loop is evaluated from the inside out. Because of the backquotes, my PATH is echoed to sed, which removes the colons separating the directories. Then the C shell goes through the foreach loop once for each directory in my PATH, setting the variable directory to the subsequent value. Each time through the loop, the -f test checks for the existence of the file; if the file exists in that directory, ls -l lists some information about it.
Here is locate at work:
limbo (bin) 93 : locate ls -rwxr-xr-x 1 root 32768 May 29 1990 /bin/ls* limbo (bin) 94 : locate vi -rwxr-xr-t 7 root 163840 Nov 29 1990 /usr/ucb/vi* limbo (bin) 95 :
Pay careful attention to the backquotes and single quotes in this script. |
It really would take an entire book (or two!) to describe fully all the ins and outs of shell scripts. The main idea here, however, is that if you use a lot of commands repetitively, you should make them into a command alias (if they're short) or drop them all into a shell script. In shell scripts, as in awk, $1 is always the first argument, $2 the second, and so on. |