When wanting to use no array for temporary storage, we can use the fact that a for
loop always iterates over an unchanging static set of elements. In a sense, we can use the loop itself as a temporary storage of the positional parameters while rebuilding the list in reverse order.
To be able to do this, we also need to empty the list on the first iteration. The code below uses a simple flag to detect whether this has to be done or not. When the list is emptied, the flag is toggled.
flag=true
for value do
if "$flag"; then
set --
flag=false
fi
set -- "$value" "$@"
done
This is unfortunately quite slow, as the list of positional parameters is effectively rebuilt in each iteration (set -- some-list
sets all positional parameters). The bash
shell takes about 50 seconds to reverse the integers between 1 and 10000, while zsh
takes just over 15 seconds.
Using Isaac's trick with ${flag-"$@"}
(which expands to "$@"
only if flag
is unset) actually makes the whole thing run slower; 1 minute 50 seconds (!) in bash
and 25 seconds in zsh
.
I'm assuming this is due to some implementation particularities in how the shells perform the test on $flag
and/or expand "$@"
for the ${flag-"$@"}
expansion (the shell might possibly expand "$@"
twice internally?).
If allowing ourselves to use an array as temporary storage (this would not be standard, but still fairly portable since we often know what shell we're writing our scripts for), we can use the value $#
(the number of positional parameters) as an index into which to store the current value while looping over the positional parameters. Decreasing this value using shift
in each iteration gives the effect of inserting values from the end of the array towards the start.
In bash
, arrays start at index 0, and since the shift
comes after the assignment, the last positional parameter will be stored at index 1 rather than 0. This has no consequence for how the code works in bash
, it will still generate the correct result, but it makes it also work in zsh
(which uses 1-based array indexes by default).
Code:
tmp=()
for value do
tmp[$#]=$value
shift
done
set -- "${tmp[@]}"
With bash
or zsh
, this uses about 0.6 seconds to reverse the integers between 1 and 10000.