Complex commands in ksh93

Storing things like collections of commands, or lists of argument lists starts getting painful even using advanced features of Bash or Zsh. Modern versions of ksh93 support complex datatypes and some simple object-oriented programming features. These make it possible to store such structures and evaluate their contents safely.

This program will generate a self-signed ca and server cert by running a series of commands stored in an array of objects that encapsulate an openssl command and associated args.

Many of these features are non-portable, unstable, poorly documented, only available in recent versions, and may change in the future. An equivalent program written in (for example) Python using the subprocess module to call external programs will be comparable in length and potentially more portable.

# Define a new type "SslCmd" with three fields: "outfile", "args", and "cmd". "cmd" has a default value of "openssl" that can be overridden by an object initializer list.
# The type defines a public method named "run".
# The result of this declaration is a new declaration command "SslCmd" that can be used in place of "typeset".
typeset -T SslCmd=(
    typeset -h 'output file' outfile
    typeset -h 'arguments passed to the command name passed to' -a args
    typeset -h 'The command to run' cmd=openssl

# A method associated with each instance that runs the command and associated args.
# _ (in this context) is a pointer to the instance, similar to "this" or "self".
function run {
    [[ -f ${_.outfile} ]] && return 1
    "${1:-${_.cmd}}" "${_.args[@]}"

function main {
    # Define a "struct" that contains configuration information.
    compound config=(

    # Declare and initialize an indexed array of SslCmd objects.
    SslCmd -a cmds=(
        (outfile=ca-key.pem; args=(genrsa -des3 -out ca-key.pem 1024))
        (outfile=ca-cert.pem; args=(req -new -x509 -days 1095 -key ca-key.pem -out ca-cert.pem -utf8 -subj "${config.caSubj}"))
        (outfile=${config.serverKey}; args=(genrsa -out "${config.serverKey}" 1024))
        (outfile=server-key.csr; args=(req -new -key "${config.serverKey}" -out server-key.csr -utf8 -subj "${config.hostSubj}"))
        (outfile=server-cert.pem; args=(x509 -req -days 1095 -in server-key.csr -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem))

    # Iterate over the keys of the "cmds" array and call each run method.
    typeset x
    for x in "${!cmds[@]}"; do
        if ! 'cmds[x].run'; then
            printf 'failed running command:\n %s\n' "${cmds[x]}" >&2
            return 1

    # Create a key
    openssl rsa -in "${config.serverKey}" -out "${config.serverKey}.insecure"
    mv -- "${config.serverKey}" "${config.serverKey}.secure"
    mv -- "${config.serverKey}.insecure" "${config.serverKey}"

    # showResults is a 2-dimensional array of argument lists to display the results.
    typeset -a showResults=(
        (rsa -noout -text -in "${config.serverKey}")
        (rsa -noout -text -in ca-key.pem)
        (req -noout -text -in server-key.csr)
        (x509 -noout -text -in server-cert.pem)
        (x509 -noout -text -in ca-cert.pem)

    for x in "${!showResults[@]}"; do
        openssl "${showResults[x][@]}"

main "$@"


Ksh93Types (last edited 2015-06-20 09:16:22 by ormaaj)