Nuts And Bolts Cross-Platform Shell Lead image: Mike Flippo, 123RF
Mike Flippo, 123RF
 

Shell scripts: Equal rights for Unix derivatives and Linux

Vive la DiffÈrence

Many system administrators manage Unix derivatives other than Linux, so shell scripts have to work across platforms. By Dr. Udo Seidel

The first lines of a shell script can be enough to cause administrators headaches. Many Linux distributions supply scripts written in Bash (Bourne Again Shell [1]), and, because not all Unix derivatives actually include a Bash shell, porting the scripts additionally means replacing the interpreter.

The Bourne shell is the interpreter of choice for this task. In fact, it should generally be your first choice because only the Bourne shell (Sh) guarantees maximum compatibility. But, again, some attention to detail is recommended.

Shell Game

More recent Linux distributions don't include a separate binary for the Bourne shell. Instead, they provide a subset of Bash. Thus, the pseudo-Sh doesn't react like a genuine Bourne shell.

A script written in this shell isn't really Bourne shell-compatible. You will notice this on Unix systems like Solaris [2], where unexpected results can sometimes confound users (Figure 1).

The Bourne shell will react differently depending on the operating system.
Figure 1: The Bourne shell will react differently depending on the operating system.

The results can be anything from interruptions caused by syntax or semantics errors to incorrect results.

Pseudo-Bourne shells are not just an issue on Linux. The Bourne shell on HP-UX [3] not only has additional functions, such as a command-line history, but is also surprisingly located in the /sbin rather than the /bin directory.

Paths, Binaries, and More Surprises

Once you have found the right shell, the next challenge to shell scripting is just around the corner. Some programs seem to suddenly disappear because they don't exist in the PATH of the user running the script. On Irix [4], for example, the ping command resides in the /etc directory. Other commands, such as last, reside in /usr/bsd/.

Each Unix variant holds its own special surprises. The obvious approach to solving this dilemma might seem to be to extend the PATH variable. Loading the variable with all the values you need for all your supported Linux and Unix flavors is the easiest approach. Although this method is easy for a system administrator to handle, it is fraught with the risk of clutter. A platform-specific extension of PATH as shown in Listing 1 is more elegant. This approach avoids overloading the script's shell environment with useless information when processing the script. However, maintaining a script of this type will tend to be more complex.

Listing 1: Platform-Specific PATH

01 PATH=/bin:/usr/bin:/usr/local/bin
02 case `uname -r` in
03    IRIX*)
04       PATH=$PATH:/etc/:/usr/bsd
05       ;;
06    HP-UX*)
07       PATH=$PATH:/sbin
08       ;;
09    ...
10 esac

Init scripts suffer from the same issue. Although almost all Unix/Linux systems keep these in the /etc/init.d directory, HP-Unix admins will need to look to /sbin/init.d. A similar problem applies to the runlevel directories.

Some Linux system administrators and Irix admins will be familiar with the chkconfig command for managing init scripts. However, you should be aware that the local chkconfig is not necessarily the chkconfig with which you are familiar.

In production use, case instructions have provided a practical approach to handling operating system-specific matters. Thus, administrators can more easily integrate new operating systems or throw old ones out.

Online Help Vital

If you manage a sufficiently varied Unix/Linux machine park, you will soon come to appreciate the online help that man gives you. The help function also indirectly documents more obstacles for cross-platform shell scripters – that is, the differences between the syntax or functionality, or both, of the commands involved.

One prominent example is the ping command. Listing 2 shows that an experienced Linux sys admin will need to rethink things when using a Solaris system. By default, the Solaris ping only tries to reach the target host once. In order to ping continuously, as on Linux, you will need to specify the -s option.

Listing 2: Ping on Solaris

# uname -sr
SunOS 5.10
# ping server
server is alive
# ping -s server
PING server: 56 data bytes
64 bytes from server.examplenet.com (192.168.5.6): icmp_seq=0. time=0.415 ms
64 bytes from server.examplenet.com (192.168.5.6): icmp_seq=1. time=0.215 ms
64 bytes from server.examplenet.com (192.168.5.6): icmp_seq=2. time=0.344 ms
64 bytes from server.examplenet.com (192.168.5.6): icmp_seq=3. time=0.256 ms
^C
----server PING Statistics----
4 packets transmitted, 4 packets received, 0% packet loss
round-trip (ms)  min/avg/max/stddev = 0.215/0.307/0.415/0.090

Another representative of operating system-specific syntax is the remote shell. This program goes by the name of remsh on HP-UX systems, whereas the rest of the Unix/Linux world calls it rsh. Additionally, you should note that the syntax does not support constructs of the type:

remsh user@server
Also,
remsh -l user server

is not supported. So, you will need to use

remsh server -l user

to achieve the desired effect.

Fortunately, the remote shell is not something you commonly need in production environments. The example in Listing 3 shows how you could handle scripting if this is relevant to your environment.

Listing 3: Handling Special Cases

01 OS=`uname -s`
02 RSH="rsh -l user"
03 RSH2=""
04 PING="ping -c1 -w 3"
05 PING2=""
06
07 case $OS in
08    HP-UX*)
09            PING=ping
10            PING2="-n 1"
11            RSH="remsh"
12            RSH2="-l user"
13       ;;
14    SunOS*)
15       PING="ping"
16       PING2=""
17       ;;
18 esac
19 ...
20
21 for i in server1 server2 server3 workstation1 workstation2
22 do
23 $PING $i $PING2 > /dev/null
24         if [ "$?" != "0" ]; then
25                 echo
26                 echo " Couldn't reach Host $i !"
27                 echo
28       continue
29         fi
30 $RSH $i $RSH2 uptime
31 done

System administrators will also experience different behavior from various programs if they change from one Linux distribution to another. If you are debugging the network on Red Hat, for example, you can tell traceroute to use the TCP protocol instead of UDP, which is really useful. Novell's Enterprise Linux systems, however, don't offer this ability before version 11 (Figure 2).

The online help shows the difference between traceroute on SLES10 and RHEL5.
Figure 2: The online help shows the difference between traceroute on SLES10 and RHEL5.

What Else? What Next?

If you want to split hairs, the topic of cross-platform shell scripting isn't even a topic in its own right, but just part of a larger task: cross-platform system management. The concept of delegating privileges in Linux is typically handled by sudo, whereas Solaris takes a Role-Based Access Control (RBAC) approach. Although a sudo package for Solaris exists, its files end up in /opt/sfw/, which doesn't make system management any easier. One approach would be to use a software package capable of handling multiple operating systems, such as OpenPKG [5] or openSoftware [6], but that subject is outside the scope of this article.

Most importantly, scripters need to know what obstacles lie ahead. Script developers should always be wary and test for certain conditions. Everything else is just a question of technique and a bit of design. How does the script handle operating system specifics? How can you make the script as robust and future-proof as possible?

The first question to answer is the one focusing on the best shell interpreter. The Bourne shell is definitely a good candidate. A Solaris system is perfect for testing scripts for genuine Bourne shell compatibility. Depending on the environment or scripting requirements, a Korn shell, or even Bash, could be your best choice of command interpreter. To make scripts easy to extend, you can use case instructions or operating system-specific shell functions.

My experience with combinations of the two has been pretty positive. One approach is to use an abstraction layer to identify the operating system and branch off in the right direction. In the simplest case, this would just be a wrapper script. Ideally, your system management software should be capable of handling this.

As your collection of scripts starts to grow, the use of a shell library becomes more or less unavoidable. Extensions and corrections all take place centrally, and you definitely need to test your scripts on all of the operating systems you support.

Conclusions

A closer look at cross-platform shell scripting can be fairly frightening, but the task is not unsolvable. The biggest obstacle is that scripters are often unaware of the pitfalls that lie ahead. When in doubt, just remember the motto: Hazard detected – hazard averted.