and now for something completely different...

sometimes in a shell script you need to do stuff that modifies the positional parameters ($@, $1, $2, etc) from the cmdline, usually via the builtin shift, maybe from using getopt(1) or similar.

for example, it's quite common to see the following in a shell script that parses cmdline flags with getopt

# parse cmdline options with getopt
TEMP=`getopt -o h123 -n $0 -- "$@"`
if [ $? != 0 ] ; then usage >&2 ; exit 1 ; fi
# overwrites $@ with getopt-generated/sanitized arguments
eval set -- "$TEMP"
# destructively iterates across $@
while true; do
  case "$1" in
        -h)
          # handle option h
          shift
        ;;
        ...
        --)
          shift
          break
        ;;
      esac
...
done

but sometimes you want to be able to restore the original positional parameters later, or otherwise quote them in a manner where they can be passed along to another script/function, log them, embed them in a string/file, etc. you can't always just stash them in another variable to use that later, because "$@" has a special meaning to the shell that can't otherwise be reproduced with another variable.

one way you could try to store/save them would be to save the original copy of $@ and use eval set -- later on to change it back to the saved variable. using a slightly simplified version of the above:

#!/bin/sh

# save the cmdline
saved="$@"

echo original command line, $# arguments: "$@"

while test -n "$*"; do echo arg: "$1"; shift; done
echo mangled command line, $# arguments: "$@"

# restore the cmdline
eval set -- "$saved"
echo restored command line, $# arguments: "$@"
while test -n "$*"; do echo arg: "$1"; shift; done

and at first glance, it seems to work:

rangda[/home/sean] ./bad.sh one two three
original command line, 3 arguments: one two three
arg: one
arg: two
arg: three
mangled command line, 0 arguments:
restored command line, 3 arguments: one two three
arg: one
arg: two
arg: three

but you may run into problems when some parameters have spaces, or quotes:

rangda[/home/sean] ./bad.sh "1: one" "2: two"
original command line, 2 arguments: 1: one 2: two
arg: 1: one
arg: 2: two
mangled command line, 0 arguments:
restored command line, 4 arguments: 1: one 2: two
arg: 1:
arg: one
arg: 2:
arg: two

rangda[/home/sean] ./bad.sh "one's one"
original command line, 1 arguments: one's one
arg: one's one
mangled command line, 0 arguments:
./bad.sh: eval: line 12: unexpected EOF while looking for matching `''

or worse, shell meta characters:

rangda[/home/sean] ./bad.sh 'i wonder what `pwd` would do'
original command line, 1 arguments: i wonder what `pwd` would do
arg: i wonder what `pwd` would do
mangled command line, 0 arguments:
restored command line, 6 arguments: i wonder what /home/sean would do
arg: i
arg: wonder
arg: what
arg: /home/sean
arg: would
arg: do

the solution is to build up the saved value a bit more manually, single-quoting each parameter (which should prevent any unintended side effects of space/quotes/metacharacters), and escaping existing single quotes inside of parameters.

# escape any single quotes in an argument
quote(){
  echo "$1" | sed -e "s,','\\\\'',g"
}

# save up a properly quoted/escaped version of "$@"
for arg in "$@"; do
  saved="${saved:+$saved }'$(quote "$arg")'"
done

echo original command line, $# arguments: "$@"

while test -n "$*"; do echo arg: "$1"; shift; done
echo mangled command line, $# arguments: "$@"

# restore the cmdline
eval set -- "$saved"
echo restored command line, $# arguments: "$@"
while test -n "$*"; do echo arg: "$1"; shift; done

let's see how it behaves:

rangda[/home/sean] ./good.sh "1 one" "2: two"
original command line, 2 arguments: 1 one 2: two
arg: 1 one
arg: 2: two
mangled command line, 0 arguments:
restored command line, 2 arguments: 1 one 2: two
arg: 1 one
arg: 2: two

rangda[/home/sean] ./good.sh "one's one"
original command line, 1 arguments: one's one
arg: one's one
mangled command line, 0 arguments:
restored command line, 1 arguments: one's one
arg: one's one

rangda[/home/sean] ./good.sh 'i wonder what `pwd` would do'
original command line, 1 arguments: i wonder what `pwd` would do
arg: i wonder what `pwd` would do
mangled command line, 0 arguments:
restored command line, 1 arguments: i wonder what `pwd` would do
arg: i wonder what `pwd` would do

while not entirely pretty, it does to do the trick. i'd be interested to know if anyone has a better way of doing this (or if there are hidden problems in what i've posted above).