Positional Parameters

The shell can be executed with positional parameters, which are numbered starting from 1. These are often used to control the behavior of shell scripts and to provide input such as a list of files to process and so on.

What follows is an interactive example of sh invoked with four positional parameters, “alpha”, “beta”, “gamma”, and “delta”:

$ sh -s -- alpha beta gamma delta
$ echo $1
alpha
$ echo $2
beta
$ echo $3
gamma

Note

The -s option flag is necessary when passing arguments in to an interactive shell because otherwise the shell would interpret its first argument as a filename of a script. Refer to SH(1P) for more information on command-line arguments.

Three shell special parameters, @, * and # are dedicated to working with positional parameters. The @ and * special parameters expand as the entire list of positional parameters and the # parameter expands to the number of positional parameters.

The # parameter is fairly self-explanatory:

$ echo $#
4

On the other hand, the @ and * parameters are unique in that they behave differently on expansion depending on whether they are enclosed in double quotes. POSIX actually doesn’t specify much about what happens when these two parameters are not enclosed in double quotes, and different shells behave differently. Typically, the result is to expand each positional parameter as a separate word:

$ printf '%s\n' $@
alpha
beta
gamma
delta
$ printf '%s\n' $*
alpha
beta
gamma
delta

Since this expansion occurs outside of a quoted context, each positional parameter word might also be subject to word-splitting and pathname expansion in contexts where either would normally occur, but it is up to the particular shell implementation whether this occurs. In bash, both versions undergo word splitting, but only * undergoes pathname expansion.

In contrast, expansion of these two special parameters within double quotes is explicitly defined by POSIX, and this is almost universally the manner in which they are used in order avoid unpredictable behavior. The reasons for this are largely an accident of history, and have been retained as one of those quirks that you’ll just need to know.

The @ special parameter expands to separate fields for each positional parameter when expanded within double quotes. Notice, importantly, this is the only mechanism by which a double-quoted expansion can result in multiple fields, since double quotes suppress the other two field-generating mechanisms: word splitting and pathname expansion. Any prefix or suffix surrounding a double-quoted expansion of @ is prepended or appended to the first and last fields respectively,

$ printf '%s\n' prefix"$@"suffix
prefixalpha
beta
gamma
deltasuffix

Unlike the @ special parameter, the * special parameter expands to a single field when expanded within double quotes. The expansion contains each positional parameter separated by the first character of IFS; if IFS is unset, <space> (’ ‘) is used, and if it is a null (empty) string, the parameters are concatenated with no separator:

$ unset IFS
$ printf '%s\n' "$*"
alpha beta gamma delta
$ IFS=:
$ printf '%s\n' "$*"
alpha:beta:gamma:delta
$ IFS=
$ printf '%s\n' "$*"
alphabetagammadelta

In general, the * is infrequently used and has only a few niche uses. On the other hand, the @ is used very often for a variety of purposes.

Modifying Positional Parameters

Within an existing shell session, two methods can be used to modify the positional parameters on the fly. The set utility can be used to change shell options and positional parameters, as if the shell had been invoked with those arguments,

$ echo "$@"
alpha beta gamma delta
$ set -- beta gamma
$ echo "$@"
beta gamma

This can be used, along with the @ special parameter, to add positional parameters to the beginning or end of the current list,

$ set -- alpha "$@" delta
$ echo "$@"
alpha beta gamma delta

The shift utility takes an optional argument n (default: 1), and discards the first n positional parameters,

$ shift
$ echo "$@"
beta gamma delta
$ shift 2
$ echo "$@"
delta

These two mechanisms allow the positional parameters to be used somewhat like a queue or a stack by pushing elements with the set builtin, and popping them from the front with the shift builtin.