Shell Script – How to Reverse Shell Arguments

posixshell-script

I know that it is possible to reverse "$@" using an array:

arr=( "$@" )

And using this answer, reverse the array.

But that requires a shell that has arrays.

It is also possible using tac:

set -- $( printf '%s\n' "$@" | tac )

But that breaks if the parameters have spaces, tabs or newlines (assuming the default value of $IFS) or contain wildcard characters (unless globbing is disabled beforehand) and removes empty elements, and requires the GNU tac command (using tail -r is slightly more portable outside of GNU systems but with some implementations fails on large input).

Is there a way to reverse shell positional arguments portably, without using an array, and that works even if arguments contain whitespaces or newlines or wildcards or are possibly empty?

Best Answer

Portably, no arrays required (only positional parameters) and works with spaces and newlines:

flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done

Example:

$ set -- one "two 22" "three
> 333" four

$ printf '<%s>' "$@"; echo
<one><two 22><three
333><four>

$ flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done

$ printf '<%s>' "$@"; echo
<four><three
333><two 22><one>

The value of flag controls the expansion of ${flag-"$@"}. When flag is set, it expands to the value of flag (even if it is empty). So, when flag is flag='', ${flag....} expands to an empty value and it gets removed by the shell as it is unquoted. When the flag gets unset, the value of ${flag-"$@"} gets expanded to the value at the right side of the -, that's the expansion of "$@", so it becomes all the positional arguments (quoted, no empty value will get erased). Additionally, the variable flag ends up erased (unset) not affecting following code.