Korn Shell Nuances
Ed Schaefer and John Spurgeon
We use the Korn shell almost exclusively. This month
we'll investigate an eclectic mix of topics pertaining to the Korn
shell and related shells:
- How can you tell which shell you are using?
- What happens if the shell isn't specified on the first line of a script?
- Setting the environment variable PS1.
Which Shell Am I Using?
From the command line, how can users determine which
shell they are currently using? Looking at the user's entry in the
/etc/passwd file only indicates the user's login shell, not
necessarily what the shell is now.
Similarly, the environment variable SHELL or shell may
be set but not reset for any spawned child shells. The Solaris 9 man page
for ksh states that SHELL is not set at all by the shell, but is set by
login on some systems.
Within a script, $0 is set to the name of the script.
But at the command line, $0 provides the name of the shell. For example, if
you are using the Korn shell, then the command "echo $0"
displays "-ksh".
This method also works for the Bourne shell. Spawn a
Bourne shell and verify the child "sh" displays:
echo $0
sh
Unfortunately, this technique doesn't work with the shells csh and tcsh.
Probably the best method of tracing which shells are
running is to use the ps command. One technique is to execute ps with no arguments. Echoing $$ obtains the process id of your current shell. By
cross-referencing the process id with the output of ps, you can determine that the current
shell is the Bourne shell (sh).
When spawning multiple shells, you can quickly lose
track of which shell is spawning what. Here is a long listing process
command, ps -l:
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
8 S 1001 11404 11399 0 51 20 e4f35898 408 e4f35904 pts/0 0:00 ksh
8 S 1001 16823 16822 0 51 20 e4d92158 62 e4d921c4 pts/0 0:00 sh
8 S 1001 16822 11404 0 41 20 e3758120 291 e3758344 pts/0 0:00 csh
8 S 1001 16824 16823 0 51 20 e3755118 408 e3755184 pts/0 0:00 ksh
In the above example, tracing the parent id, PPID,
tells you that parent shell ksh, PID 11404, spawned a csh followed by sh,
followed by another ksh.
Executing ps -p $$ lists only data from the current process id. The last
line contains the current shell:
PID TTY TIME CMD
16824 pts/0 0:00 ksh
A script can use a variety of tools to parse the output of ps and
pick out the shell. In the following example, the xargs command spits out all the words
on a continuous line, and the awk command grabs the last field:
ps -p $$ | xargs | awk ' { print $NF } '
If you prefer to eliminate xargs and its plumbing,
save the last field of every line read, and print only the last one at the
end of the input:
ps -p $$| awk ' { lf=$NF } END { print lf } '
Alternatively, if your ps command supports the -o format option, execute ps with the comm format:
ps -o comm -p $$
The output is:
COMMAND
ksh
The comm format displays only the command being executed -- ksh in this case. Now, by using the formatting
option, simply grab the last line to obtain the shell:
ps -o comm -p $$ | tail -1
Specifying a Shell on the First Line of a Script
One of the first lessons a new shell programmer learns
is not to rely on the default environment. It's always a good idea,
for example, to invoke the shell of choice on the first line of a script
like this:
#!/bin/ksh
Not only must the shell specification be on the first
line, but the "#" character must be in the first column. What
happens if these conditions are not met?
A simple test script illustrates the effect. To
begin, invoke the Bourne shell from the command line, so the script is
interpreted by sh if a shell is not explicitly specified on the first line
of the script. Next, place the following commands in a script:
#!/bin/ksh
echo $(ps -p $$)
Executing the script produces the following output on our system:
PID TTY TIME CMD 16057 pts/3 0:00 test.ss
Next, try placing a comment on the first line, so the script looks something like this:
# Doh!
#/bin/ksh
echo $(ps -p $$)
Execute the script again, and this error displays:
./test.ss: syntax error at line 3: '(' unexpected
The problem is that the Bourne shell is interpreting
the script instead of the Korn, and the ksh command substitution syntax $() is not supported by sh. The same thing happens if white
space precedes the string #/bin/ksh on the first line of the script.
Since many Korn shell scripts appear to work fine when
interpreted by the Bourne shell, problems with where the shell
specification is located may not always be obvious. We learned this the
hard way.
Setting the Primary Prompt String
Using the environment variable PS1, users can create a
dynamic prompt string. Here's a common example using Korn shell
environmental variables:
export PS1='$LOGNAME@$SYSNAME:$PWD'":> "
Since environmental variable PWD is dynamically set by the cd command,
each time the directory changes, our PS1 displays the system name, the user
name, and the current working directory.
According to Bolsky and Korn, the "ksh performs
parameter expansion, arithmetic expansion, and command substitution on the
value of PS1 each time before displaying the prompt". However, not
all implementations of the Korn shell provide all of these features.
The version of the Korn shell we use most often
performs parameter expansion but not command substitution. Under these
conditions, suppose we want the prompt string to only display the last two
directories of the current working directory's full pathname. For
example, if PWD were:
/home/eds/jspurgeo/myentry/src
we only want to see:
myentry/src
Here's one way to solve the problem:
export PS1='${PWD##/*/*}${PWD#${PWD%/*/*}} >'
To simplify the explanation, we break the solution into three pieces:
References
Bolsky, Morris I., David G. Korn. 1995. The New Kornshell Command and
Programming Language. Upper Saddle River, NJ: Prentice Hall PTR.
John Spurgeon is a software developer and systems
administrator for Intel's Factory Information Control Systems, IFICS,
in Aloha, Oregon. He is currently preparing to ride a single-speed bicycle
in Race Across America in 2007.
Ed Schaefer is a frequent contributor to Sys Admin. He is a software
developer and DBA for Intel's Factory Information Control Systems,
IFICS, in Aloha, Oregon. Ed also hosts the monthly Shell Corner column on
UnixReview.com. He can be reached at: shellcorner@comcast.net.
|