Search code examples
shellshposix

Modifying positional parameters while iterating over them in POSIX sh


Is this portable?

filter() {
set -- CUT "$@"
for x; do
  if test "$x" = CUT; then
    set --  # ignore args upto here
  else  # perhaps more filtering
    set -- "$@" "$x"
  fi
done
printf "'%s' " "$@"; echo
}
filter "$@"

I.e. can I change "$@" while iterating over it? Does the for compound duplicate the implicit array?

filter 1 2 CUT 3

Seems to work in dash, ash, busybox sh.


Solution

  • Yes, POSIX does allow that. It can be inferred from the section titled The for Loop (quoted below, emphasis mine) that the loop keeps its own private copy of the list of items it is to iterate over, and that changes made to the shell execution environment while the loop is performed will not have any effect on said copy.

    for name [ in [word ... ]]
    do
      compound-list
    done
    

    First, the list of words following in shall be expanded to generate a list of items. Then, the variable name shall be set to each item, in turn, and the compound-list executed each time. Omitting:

    in word...
    

    shall be equivalent to:

    in "$@"
    

    In other words, it is guaranteed that the loop in your program iterates over the initial list of positional parameters because the implied expansion of "$@" precedes set -- and set -- "$@" "$x".