From time to time, I’m involved into a trolling conversation when any linux kiddie tells me:

Bash is really the superior shell

I totally disagree, but as I’m getting older, I don’t argue anymore.

Anyway, in this post I will expose two arguments, or I should say two reasons, why I usually use ksh93 to run my scripts.

Note I’m really talking about the engine of the script, (the shebang definition). set I’m used to the bourn shell syntax therefore I also exclude any C shell from the comparison. My $SHELL for interactivity is zsh because it’s efficient enough and it has a bunch of really cool features I won’t discuss in this post (maybe later)

Read, loops, forks and efficiency…

More than 10 years ago, as I was working for a project at IBM, my excellent team leader told me to refer to this book: Unix Power Tools. I did learn a lot with it.

And one feature I’ve always used is the while read loop.

The use case

Let’s take this script as example:

1$ cat test                                                                                                         
2for i in $(seq 1 500)
3do
4    echo $i | read a
5    echo -ne "$a\r"
6done
7echo ""

It simply iterate 500 times and display the counter on the screen.

The result of execution

Let’s execute it in different shells

 1for i in bash zsh ksh                                                                                         
 2do
 3    echo "$i =>"
 4    eval $i test
 5done
 6bash =>
 7
 8zsh =>
 9500
10ksh =>
11500

Bash is the only one which does not display the expected result. The explanation is that the shell sees a pipe and the fork the process. The assignation to the variable a is in another context and therefore, when the father wants to display $a in the current shell, the variable is empty.

Wait, but why does ksh (and zsh) do display the correct result ? Simply because ksh and zsh have noticed that the command after the pipe was a builtin, and therefore that it was un-useful to fork.

Strace to the rescue…

To prove it, let’s check for syscalls with the strace tool, and count how many clones and calls are performed:

 1$ for i in bash zsh ksh                                                                                         
 2do
 3    echo "$i =>"
 4    strace -c  $i  test 2>&1 | egrep "clone|calls"
 5done
 6bash =>
 7% time     seconds  usecs/call     calls    errors syscall
 856.05    0.067081          67      1001           clone
 9zsh =>
10% time     seconds  usecs/call     calls    errors syscall
1171.57    0.057681         115       501           clone
12ksh =>
13% time     seconds  usecs/call     calls    errors syscall
1468.50    0.042059          84       500           clone

quod erat demonstrandum, twice as much clone in bash thant in ksh|zsh.

Efficiency

Of course this as an impact on performances, because fork are expensive, let’s query the execution time:

 1for i in bash zsh ksh                                                                                         
 2do
 3    echo "$i =>"
 4    eval time $i test
 5done
 6bash =>
 7
 8bash test  0,17s user 0,86s system 95% cpu 1,079 total
 9zsh =>
10500
11zsh test  0,08s user 0,46s system 82% cpu 0,648 total
12ksh =>
13500
14ksh test  0,07s user 0,46s system 65% cpu 0,819 total

This sounds clear to me…

The KSH93 Getopts unknown feature

Another cool feature I’ve discovered recently is the little addon of the getopts feature.

I wanted to use the getopts built in in a script. As usual, I did RTFM (because I never know when to use colon etc.).

Here is the extract of the man page of ksh93 relative to the getopts function:

This particular sentence, in the middle of the documentation peaked my interest

The option -? causes getopts to generate a usage message on standard error.

What? We can generate usage with getopts?

Cool, any script should be documented, but any documentation should not be difficult to implement.

I did googled and found this web page which is an extract from this book Learning the Korn Shell

An example is sometimes better than an explanation (and the book is complete on this subject)

The example

The script

 1#!/bin/ksh
 2
 3ENV=dev
 4MPATH=/tmp
 5##
 6### Man usage and co...
 7
 8USAGE="[-?The example script v1.0]"
 9USAGE+="[-author?Olivier Wulveryck]"
10USAGE+="[-copyright?Copyright (C) My Blog]"
11USAGE+="[+NAME?$0 --- The Example Script]"
12USAGE+="[+DESCRIPTION?The description of the script]"
13USAGE+="[u:user]:[user to run the command as:=$USER?Use the name of the user you want to sudo to: ]"
14USAGE+="[e:env]:[environnement:=$ENV?environnement to use (eg: dev, prod) ]"
15USAGE+="[p:path]:[Execution PATH:=$MPATH?prefix of the chroot]"
16USAGE+="[+EXAMPLE?$0 action2]"
17USAGE+='[+SEE ALSO?My Blog Post: http://blog.owulveryck.info/2015/11/30/ksh93-cool-features-for-scripting]'
18USAGE+="[+BUGS?A few, maybe...]"
19
20### Option Checking
21
22while getopts "$USAGE" optchar ; do
23    case $optchar in
24        u)  USER=$OPTARG
25        ;;
26        e)  ENV=$OPTARG
27        ;;
28        p)  PATH=$OPTARG
29        ;;
30    esac
31done
32shift OPTIND-1
33ACTION=$1

The invocation

Here are two singing examples of the usage output (sorry, I’m tired)

Ballad of a thin man

 1$ ./blog.ksh --man
 2NAME
 3  ./blog.ksh --- The Example Script
 4
 5SYNOPSIS
 6  ./blog.ksh [ options ]
 7
 8DESCRIPTION
 9  The description of the script
10
11OPTIONS
12  -u, --user=user to run the command as
13                  Use the name of the user you want to sudo to: The default value is owulveryck.
14  -e, --env=environnement
15                  environnement to use (eg: dev, prod) The default value is dev.
16  -p, --path=Execution PATH
17                  prefix of the chroot The default value is /tmp.
18
19EXAMPLE
20  ./blog.ksh action2
21
22SEE ALSO
23  My Blog Post: http://blog.owulveryck.info/2015/11/30/ksh93-cool-features-for-scripting
24
25BUGS
26  A few, maybe...
27
28IMPLEMENTATION
29  version         The example script v1.0
30  author          Olivier Wulveryck
31  copyright       Copyright (C) My Blog

I’m gonna try with a little help (from my friends)

1$ ./blog.ksh --help
2Usage: ./blog.ksh [ options ]
3OPTIONS
4  -u, --user=user to run the command as
5                  Use the name of the user you want to sudo to: The default value is owulveryck.
6  -e, --env=environnement
7                  environnement to use (eg: dev, prod) The default value is dev.
8  -p, --path=Execution PATH
9                  prefix of the chroot The default value is /tmp.

And let’s try with an invalid option…

1  ./blog.ksh -t
2./blog.ksh: -t: unknown option
3Usage: ./blog.ksh [-u user to run the command as] [-e environnement] [-p Execution PATH]

Conclusion

By now, KSH93 remains my favorite engine for shell scripts, but is sometimes replaced by ZSH.

Actually, ZSH seems as “smart” and efficient, but this getopts feature is really nice for any script aim to be distributed widely.