Date:        Mon, 15 May 2017 18:36:58 +0200
    From:        Steffen Nurpmeso <stef...@sdaoden.eu>
    Message-ID:  <20170515163658.b7ljs%stef...@sdaoden.eu>

  | Is it at all possible to store the parameter stack in a variable
  | "x" in order to restore it exactly "as-is", in the shell?

Yes, it is, you just need to think in a slightly different way when
programming in shell (and other string manipulation languages) than
you do in traditional programming languages.

What you need first is a function which quotes a string, used like
        result=$(quote "$string")
in a way that you can later do
        eval "string=${result}"
and get back the original string - whatever it contains.

Fortunately, in shell, quoting is fairly simple (even if it sometimes
seems complex), single quotes leave the content totally uninterpreted,
and absolutely anything can be quoted with single quotes, except a '
itself, which makes the quoting function relatively simple to design.

The one issue, as with all shell functions in a standard shell, is that
they have no local variables (except the numeric params) and it is hard
to write any complex function without at least one, so you need to avoid
stepping on the namespace of the rest of your code.   Here we need just
four extra vars (it might be possible to avoid them, but I haven't
explored how ... the shells I use all have a "local" special builtin
added, which avoids this problem...)

Alternatively, you could implement this as an external #! script, that
would be a lot slower, but would at least avoid that issue.

quote() {
        case "$1" in
        *\'*)   ;;               # the harder case, we will get to below.
        *)      printf "'%s'" "$1"; return 0;;
        esac

        _save_IFS="${IFS}"     # if possible just make IFS "local"
        _save_OPTS="$(set +o)"  # quotes there not really needed.
        IFS=\'
        set -f
        set -- $1
        _result_="${1}"        # we know at least $1 and $2 exist, as there
        shift                  # was one quote in the input.

        for __arg__
        do
                _result_="${_result_}'\\''${__arg__}"
        done
        printf "'%s'" "${_result_}"

        # now clean up

        IFS="${_save_IFS}"              #none of this is needed with a good 
"local"...
        eval "${_save_OPTS}"
        unset __arg__ _result_ _save_IFS _save_OPTS

        return 0;
}

Once you have that function, you can use it to do all kinds of stuff,
perhaps something like ...

$ x="a string with * \$foo  # \" and ' in it... and not forget \\"
$ printf '%s\n' "$x"
a string with * $foo  # " and ' in it... and not forget \
$ y=$(quote "$x") 
$ printf '%s\n' "$y"
'a string with * $foo  # " and '\'' in it... and not forget \'
$ z="many ' chars''' in one silly ''' string"
$ y="${y}:$(quote "$z")"
$ printf '%s\n' "$y"
'a string with * $foo  # " and '\'' in it... and not forget \':'many '\'' 
chars'\'''\'''\'' in one silly '\'''\'''\'' string'
$ # now unpack y again
$ IFS=:
$ set -- $y
$ eval "x=$1"
$ eval "y=$2"
$ printf 'x=<<%s>>\ny=<<%s>>\n'  "$x" "$y"
x=<<a string with * $foo  # " and ' in it... and not forget \>>
y=<<many ' chars''' in one silly ''' string>>
$ stack="$*"                    # IFS is still ':'
$ printf '%s\n' "${stack}"
'a string with * $foo  # " and '\'' in it... and not forget \':'many '\'' 
chars'\'''\'''\'' in one silly '\'''\'''\'' string'
$ # Or to push
$ stack="$(quote $x):$*"
$ # or to pop (note, this is an alternative to the push, not following it)
$ shift; stack="$*"
$ # these are all with the stack currently expanded into $@ of course...
$ # so would normally be being performed in some other function(s).

Personally I have never (I think, not once) ever really thought that I
wold prefer a shell that had an "array" data type, they're really just
not required.   If your application needs so much speed that implementing
things without it is too slow (because of all the string manipulation that
happens) then it would probably be better written in a compiled language
in the first place.

How you actually make use of all of this depends upon the needs of
your application, naturally.

Just remember to always quote variable references "$x" unless you are
100% certain what the content of the variable is, eg: as above with $y
where we know it is the result of the quote function, so is safe.

kre

ps: all this is more or less just off the top of my head as I write this,
and while the output I showed is from a shell really executing that code,
that is as much testing as I have given it ... there might still be a
little more needed that thorough testing would reveal.

Reply via email to