Bash – Is parameter expansion on $@ not supported by the sh shell

bashdashshell

I posted an answer to a question on AU, and I found that parameter expansion on $@ doesn't work with the sh shell:

<infile xargs -d'\n' sh -c 'echo "${@%%/*}"' _

but it works fine in bash. Is this expected behavior of the sh shell, and how can I perform expansion there?

Additionally, I know that with the -n1 option of xargs I can pass only one line to the command at a time, but I was interested in whether sh can expand $@:

<infile xargs -d'\n' -n1 sh -c 'echo "${0%%/*}"'

infile contains:

A1 /B1/C1
A 2/B2/C2
A3/B3/C3

Best Answer

Yep, dash appears to be less than useful here. Though it's not at fault, strictly speaking, as ${@%...} is unspecified by POSIX:

The following four varieties of parameter expansion provide for substring processing. [...] If parameter is '#', '*', or '@', the result of the expansion is unspecified.

It's weird though, it seems that if an expansion like that modifies the end of one positional parameter, it drops the following ones. But not if it doesn't actually modify the end:

$ dash -c 'set -- foo bar; printf "<%s>\n" "${@%o}";'
<fo>
$ dash -c 'set -- foo bar; printf "<%s>\n" "${@%x}";'
<foo>
<bar>
$ dash -c 'set -- foo bar doo; printf "<%s>\n" "${@%r}";'
<foo>
<ba>

Bash, ksh and Zsh all seem to handle "${@#...}" and "${@%...}" by processing each positional parameter independently, which would seem the useful thing to do.

I suppose the obvious workaround for dash is to make the modification one argument at a time:

for x in "$@"; do echo "${x%%/*}"; done

For what it's worth, the behaviour of the prefix/suffix removal expansions used on $* also varies between shells. Bash and ksh seem to modify the parameters first and join them after that, while Zsh and dash join the parameters first and modify the concatenated string:

$ zsh -c 'set -- ax bx; printf "<%s>\n" "${*%%x*}";'
<a>
$ bash -c 'set -- ax bx; printf "<%s>\n" "${*%%x*}";'
<a b>
Related Question