Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent ksh on Linux to overwrite a global variable by a local variable?

Tags:

linux

ksh

aix

I am involved in the process of porting a system containing several ksh scripts from AIX 6.1 to SUSE-Linux. I have come across the following difference in the way ksh behaves on the two systems:

# LocalVar.sh 

test_loc_var()
{ 
typeset -t var 
var=localvariable 
echo "var = $var" 
} 

typeset var=globalvariable 

echo "var = $var" 
test_loc_var 
echo "var = $var"

The correct result on AIX is:

var = globalvariable 
var = localvariable 
var = globalvariable 

The wrong result on Linux is:

var = globalvariable 
var = localvariable 
var = localvariable 

My questions are:

  • Is there an environment variable that I can set to get Linux's ksh to behave like on AIX? Failing that:
  • Is there an option on Linux's ksh to get the required behavior? Failing that:
  • What changes in the code have I to do, to get the desired behavior on Linux?

Note:

  • I have already tried to declare the variable with "local", but that returned an error on Linux, on AIX it works.

The following table summarizes the two systems:

uname -s                 |    Linux                    AIX        
uname -r                 |    2.6.16.60-0.54.5-smp     1
which ksh                |    /bin/ksh                 /usr/bin/ksh
rpm -qa | grep -i ksh    |    ksh-93s-59.11.35         -
lslpp -l | grep -i ksh   |    -                        bos.rte.shell 6.1.8.15 APPLIED Shells (bsh, ksh, csh)
like image 814
Cologne2202 Avatar asked Mar 28 '13 11:03

Cologne2202


1 Answers

TL;DR: For trivial cases: switch your function definition syntax from f() compound-command to function f { ...; }. For complex cases: Depend on ksh93-only (much more flexible), use the below absurd hacks (hard), rewrite to be strictly POSIX conforming (maybe hard, inflexible), rewrite in a real language (but shells are nice sometimes).

There is no "Linux ksh". It behaves the same on all systems and only depends on the version you're using.

AIX ships a modified ksh88. ksh88 had a dynamic scope system, similar to Bash and all other shells that support locals, but unlike ksh93. In order for locals to work under ksh93, you must use the "modern" function name { ; } syntax, rather than the POSIX syntax to define functions. This may or may not be required in ksh88, as it isn't documented and there is no possible way for me to test, as ksh88 is proprietary software and most likely isn't even built to run on modern x86 hardware.

If the above is correct, and your scripts were written for ksh88, simply switching function definition syntax is enough for local variables to at least function. However, While ksh93's static scope is vastly superior to the dynamic scope of other shells, it does cause a serious portability problem -- probably one of the most difficult to work around in all of shell scripting.

If you need portable locals, there are no fantastic solutions. I've come up with two techniques which "break" ksh scope to be more like ksh88/bash/mksh/zsh etc.

The first works in non-broken POSIX shells.

#!/bin/sh
# (Partially) Working shells: dash, posh, bash, ksh93v, mksh, older zsh
# Broken shells: current zsh, busybox sh, non-bleeding edge alpha ksh93, heirloom

f() {
    if ! ${_called_f+false}; then
        # Your code using "x"
        for x; do
            printf '%s, ' "$x"
        done
    else
        # This hackishly localizes x to some degree
        _called_f= x= command eval typeset +x x 2\>/dev/null \; f '"$@"'
    fi
}

# demonstration code
x='outside f'; printf "$x, "; f 1 2 3; echo "$x"

The second method only works in ksh-like shells and involves explicitly passing everything by reference and using indirection extensively.

#!/usr/bin/env ksh
# bash, ksh93, mksh, zsh
# Breaking things for dash users is always a plus.

# This is crude. We're assuming "modern" shells only here.
${ZSH_VERSION+false} || emulate ksh
${BASH_VERSION+shopt -s lastpipe extglob}
unset -v is_{ksh93,mksh}
case ${!KSH_VERSION} in
    .sh.version) is_ksh93= ;;
    KSH_VERSION) is_mksh=
esac

function f {
    # We want x to act like in dynamic scope shells. (not ksh93)
    typeset x
    g x
    typeset -p x
}

function g {
    # Note mksh and bash 4.3 namerefs kind of suck and are no better than eval.
    # This makes a local of a pointer to the variable arg of the same name.
    # Remember it's up to the programmer to ensure the sanity of any NAME
    # passed through an argument.

    ${is_ksh93+eval typeset -n ${1}=\$1}
    typeset y=yojo

    # mksh... you fail at printf. We'll try our best anyway.
    eval "$(printf %${is_mksh+.s%s=%s%.s }s=%q  "$1" ${is_mksh+"${y@Q}"} "$y")"
}


f

I only recommend either of these if you're one of the few that requires writing robust library code that has to be portable too.

like image 162
ormaaj Avatar answered Sep 28 '22 12:09

ormaaj