Search code examples
bashshellparameter-expansion

Shell parameter expansion: $# vs. ${#@}


Within a shell script, as far as I can tell, $# and ${#@} behave identically, both giving the number of positional parameters. Is there any difference between the two, and in what context would one be preferable over the other?


Solution

  • ${#@} / ${#*} is the same as $# in most POSIX-like shells, but not all - a notable exception is dash, which acts as sh on Ubuntu systems.

    $# is the POSIX-compliant form, so it is the safe (portable) choice (from the POSIX spec, prefix $ implied):

    # Expands to the decimal number of positional parameters.


    Optional background information

    The POSIX shell spec is largely based on the historical Bourne shell, whose only array-like construct is the sequence of positional parameters ($1, $2, ...), with $# containing the count of positional parameters, $* expanding to a space-separated list of the parameter values that is then subject to word-splitting, and "$@" - in a double-quoted context - expanding to the positional parameters as originally specified (even if they contain embedded whitespace).

    The following discusses bash, ksh, and zsh; dash, which acts fundamentally differently, is discussed at the bottom.

    bash, ksh, and zsh:

    POSIX-compatible shells such as ksh and bash later generalized this pseudo-array to provide bona fide array variables, whose syntax borrowed from the positional-parameter syntax (zsh supports this syntax too, but has its own, simpler syntax as well):

    ${arr[*]} and "${arr[@]}" function analogously to $* and "$@", and both ${#arr[@]} and ${#arr[*]} correspond to $#.

    Perhaps in a nod to the original syntax, these shells (which also includes zsh, whose array syntax is simpler, however) also chose to support ${#@} and ${#*} for symmetry, where you can think of @ / * as the all-elements subscripts of the implied array, i.e., the pseudo-array of positional parameters.

    As for symmetry regarding element extraction:

    • Something like ${@[2]} to mirror $2 works only in zsh, not in bash and ksh.

    • The equivalent slicing syntax works in all of them, however: ${@:2:1}


    dash:

    dash, the default shell (/bin/sh) on Ubuntu systems, dash, is mostly restricted to POSIX-only features, and does not support arrays at all.

    As a consequence, it treats ${#@} / ${#*} differently: it interprets @ and * as the scalar string list of the (expanded) parameters and returns that string's length.
    In other words: in dash, echo "${#@} / echo "${#*} is the equivalent of: list="$@"; echo "${#list}".

    In the absence of support for arrays altogether, dash fittingly neither supports ${@[2]} nor ${@:2:1}.