comparing shells

I would define a shell as a program that prompts a user for commands and tries to run them. There are a lot of shells, and most of them have features beyond that. But that is the definitive qualification that makes a program be a shell, or not, provided only it can command-execute. Shells have other features as well but these are bells and whistles, extras. The differences among shells from a user's perspective lie in their respective sets of particular extras.

A car is defined by its ability to automotively transport. If it does that it's a car, if not not. There are a lot of cars, and most of them have features beyond that. But that is the definitive qualification that makes a machine be a car, or not, provided only it can transport. The other features are bells and whistles, options. The differences between similar cars from a consumer's perspective lie in their options, and which ones a given car might have.

So just to get the feel for the nature and differences among shells, let's put 3 of them side by side and touch upon a few selected differences. To do that, Ellie Quigley's UNIX Shells by Example textbook offers a "Shell Programming Quickstart" chapter in which a short script is re-written to operate, with functional equivalency, under 4 shells:

 - Bourne shell
 - bash shell
 - C shell
 - Korn shell

This exercise uses 3 of the above 4 shells. It omits the Bourne shell. That is the very early UNIX shell, has been much augmented in the subsequent 3, and isn't even installed anymore in most linux distributions (you can ask one of the other shells, bash, to rewind its feature set and behave as closely as possible to the old Bourne shell, "If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible" per bash man page. As well, beyond just startup behavior, bash offers a compile-time option --enable-minimal-config: "This produces a shell with minimal features, close to the historical Bourne shell."). The Bourne shell itself, however, has been largely superseded in real-world usage.

Below are sections about several things the shells do-- Variable assignment, array handling, command substitution, arithmetic expansion, and looping. Under each are commands to do it. However, because of differences between shells those commands may work in one but fail in another. Every command shown works in at least one shell. But beyond that, it may work in one of the remaining two but not the other. Or in both, making it universal (in this small 3-shell universe at least). Or in neither.

Some things shells do

Variable assignment

x=5
echo  $x

set y=6
echo $y


Arrays

Array assignment:

foods=(sushi burger pasta)

declare -a languages=(french, swahili, urdu)

planets=([1]=mercury [3]=earth [9]=pluto)

set subjects=(reading writing arithmetic)

set -A colors  red blue yellow

cities[2]=chicago
cities[1]="new york"
cities[5]=cleveland

Printing an array element:

echo $planets[3]

echo ${planets[3]}

print ${planets[3]}

Printing all (non-null) array elements:

echo $planets

echo $planets[*]

echo ${planets[*]}

Printing length/size:

echo ${#planets}

Destroying an array

unset planets

Command substitution

echo `date`

echo $(date)


Arithmetic expansion

sum=2+3

@ sum= 2 + 3

sum=$[2+3]

sum=$((2+3))

((sum=2+3))

let sum=2+3

Try the above in each shell. Afterward, if there was no error message, check for the expected value in the variable:

echo $sum


for loop

Put each of these into files, named "script1" and "script2" repectively.

for vehicle in bicycle helicopter truck    
do
    echo $vehicle
done

foreach vehicle (bicycle helicopter truck)
    echo $vehicle
end

To run these in the respective shells, don't do so the usual way by submitting them to the shells as commands themselves. Rather (from any of the 3 shells) explicitly run the shell that you want to run the script, like this:

bash script1
tcsh script1
ksh script1
bash script2
tcsh script2
ksh script2

( this device is a necessary detail because when you call the script directly, the shell you are running might transparently call one of the others to do the work...

"If the file has execute permissions but is not an executable to the system (i.e., it is neither an executable binary nor a script that specifies its interpreter), then it is assumed to be a file containing shell commands and a new shell is spawned to read it. The shell special alias may be set to specify an interpreter other than the shell itself." --tsch man page

The "special shell alias" defaults in tcsh to /bin/sh which, on our systems, symbolically links or points to /bin/bash. Launch your script from tcsh and bash is called to actually run it. We don't want that.)

 

The experiment for you to perform:

Part 1 -  read shell description (nutshells?)

Find out about our 3 shells of primary interest and several others:

yum  info  bash  dash  ksh  mksh  tcsh  yash  zsh  |  less

Study the output; read the description of each shell. The book also contains a summary chart of salient feature differences in the appendix entitled "Comparison of the Shells." Study it now.

 

Part 2 - test the features in the various shells

Please give yourself a terminal window environment for each shell. Within each one, try each of the commands or command sequences shown in blue in the above section entitled "Some things shells do" and see what happens. Try each command in each shell. Don't worry about learning the particulars of the features in any depth. We aren't here to learn exactly how they work, but to experience the fact that how they work differs across shells. Your initial setup might look something like this:

These are 3 terminal windows on the same machine. The default login shell for user account "david" on the machine is bash. So, in the lower 2 windows, I had to explicitly run the other 2 shells. Then in all 3, just to verify, I checked to see which shell I ended up running by generating a process list and searching it for one that contained the process number ($$) of my shell.

Now we can start trying shell features to see how they may differ. Please understand that everything we test here is a part and parcel of the shell's own code. Shells have among other features some built-in commands. Most shells for example have a builtin named echo. You will learn that there exist also separate, free-standing programs by the same name. For example you might see /bin/echo in your filesystem. But running something from a shell that lies outside it, teaches nothing about the shell.

 

Part 3 - programming the bash version of an example, The Party Program

One program, three shells. Suppose you want a cross-platform shell script. Within limits code that executes under one shell may do so under another. But that can't be assumed. To achieve script portability requires care and conscious avoidance of non-portable code where possible. This requires you to know shell differences, what will port and what won't, in some detail.

An example of functionally equivalent scripts for bash, csh, and ksh is provided in Ellie Quigley's UNIX Shells by Example. All 3 have identical external behavior. But to accomplish that they have internal code differences, each according to the shell for which it is written. Quigley's Chapter 2 compares and contrasts features among the shells. Her script is called "the party program," about planning a potluck. There is a list of guests and a list of foods. The program prints a message to each guest, inviting him and asking him to bring a particular food. Achieving this requires a number of coding differences among the script versions. Per your instructor obtain the the-party-program-csh-ksh.zip. It contains 2 of the 3 versions, those for csh and ksh. The bash version is omitted. Unpack the file:

cd
unzip  the-party-program-csh-ksh.zip

A directory named the-party-program results.

cd  the-party-program

In it are the 2 program versions, named:

    csh-party-program
    ksh-party-program

Open the csh version. Study the code until you understand the essentials:

less  csh-party-program

Run each of the 2 versions appropriately, noting the equivalency of output:

tcsh   csh-party-program
ksh   ksh-party-program

Check the first lines in these scripts:

head  -1  [ck]sh-party-program

and consider whether running them directly (instead of as arguments to the shells they're written for) would be equivalent. Would it? Under all circumstances?

Let's focus on the differences between their code. First:

diff  csh-party-program  ksh-party-program  |  less

Study it. Second and more accessibly, open both of them at once using the vim editor's diff split feature:

vim  -d   *sh-party-program

(You will get a split screen, with one of the 2 programs on the left and the other on the right. Color highlighting reveals where differences lie. Whenever you are ready, you can quit both open files with a single vi command:  :qa .

Scrutinize the code and where you see differences refer back to the related portions of Quigley's section 2.3 "The C and TC Shell Syntax and Constructs" and section 2.5 "The Korn Shell Constructs" to understand these features' syntax rules in each shell.

Now, referring to section 2.6 "The Bash Shell Constructs," adapt the code so that it runs in bash as it does in the other shells. It must run identically. You are finished when your bash version generates output identical to the other two as evidenced for example by:

[root@garcon the-party-program]# ./bash-party-program > bash.out
[root@garcon the-party-program]# ./csh-party-program > csh.out
[root@garcon the-party-program]# ./ksh-party-program > ksh.out
[root@garcon the-party-program]#
[root@garcon the-party-program]# ls -l *sh.out
-rw-r--r--. 1 root root 901 Oct  7 19:06 bash.out
-rw-r--r--. 1 root root 901 Oct  7 19:06 csh.out
-rw-r--r--. 1 root root 901 Oct  7 19:06 ksh.out
[root@garcon the-party-program]#
[root@garcon the-party-program]# diff bash.out csh.out
[root@garcon the-party-program]# diff bash.out ksh.out
[root@garcon the-party-program]#
[root@garcon the-party-program]# md5sum *sh.out
e2ae05392c94852df1d4d3b7cf28433d  bash.out
e2ae05392c94852df1d4d3b7cf28433d  csh.out
e2ae05392c94852df1d4d3b7cf28433d  ksh.out
[root@garcon the-party-program]#