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.