Shell scripts: Equal rights for Unix derivatives and Linux
Vive la DiffÈrence
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 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).
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.