Bash – Why $0 is not a positional parameter

bashparametershell

I have read that positional parameters start at $1 (for example: $1, $2, $3 and so on are positional parameters). But $0 is not a positional parameter.

But why $0 is not a positional parameter?

I think this could be a reason, but not sure:

Positional parameters only take their values when a script is executed. For example: if we do ./myScript Hello, then $1 will have the value Hello. But $0 can take its value on two occasions: when a script is executed (it would have the value of the script name), and when bash itself is executed without a script (it would have the value bash or -bash).

Best Answer

@ikkachu has explained it better than I even could. I'll just add a history note.

The shell that came with the first versions of Unix (later named the Thompson shell) had no variable, but you could already write simple scripts that would take parameters.

     sh [ name [ arg1 ... [ arg9 ] ] ]

The name is the name of a file which will be read and in‐
terpreted.   If  not given, this subinstance of the shell
will continue to read the standard input file.

In command lines in the  file  (not  in  command  input),
character  sequences of the form "$n", where n is a digit
0, ..., 9, are replaced by the nth argument to the  invo‐
cation of the shell (argn).  "$0" is replaced by name.

$1...$n were already the arguments and $0 the name of the script (not the 1st argument) but were not called positional parameters then.

Note that back then, $1 was literally replaced with the first argument before shell interpretation.

For instance, a script that had:

echo $1

called as

sh script 'foo; echo bar'

would run echo foo; echo bar. That shell was a very simple one written for computers with few hundred kilobytes of memory.

The Bourne shell came almost a decade later (in the late 70s) with a version of Unix that introduced the environment and other goodies.

The Bourne shell did come with variables and a lot more programming constructs.

The positional parameter terminology, at least when it comes to Unix shells, was introduced by the Bourne shell, and referred to the same thing, $1... $n for the arguments of the script (and $0 still the script name). Like in the Thompson shell, you could only refer to the first 9 arguments ($1 to $9) with positional parameters (but use shift or "$@" or for i do loops to access the rest) (and that also explains (backward portability) why you need ${10} instead of $10 in most modern sh implementations for the 10th ones).

This time, sh script 'foo; echo bar' no longer caused echo bar to be run, but still the Bourne shell introduced that infamous split+glob, I suppose not to break backward compatible too much with the Thompson shell so that script 'foo *' where script has echo $1 still did call echo with foo and the list of files in the current directory as arguments (like in the Thompson shell but this time through a different mechanism).

scripts were also called shell procedures (note that the Bourne shell didn't have functions yet):

2.0 Shell procedures

The shell may be used to read and execute commands contained
in a file.  For example,

         sh file [ args  ]

calls the shell to read commands from file.  Such a file  is
called  a  command  procedure or shell procedure.  Arguments
may be supplied with the call and are referred  to  in  file
using  the  positional parameters $1, $2...

Functions were first introduced in the Korn shell (based on the Bourne shell) in the early 80s with a

function foo {
  ...
}

syntax.

Functions were eventually added to the Bourne shell as well later in SysVR2 (1984) with a different syntax:

foo() any-command

(but with unexpected behaviour when any-command was a simple command and had redirections which is probably the reason why POSIX only requires compound commands (like { ...; } the most often used one) be recognised there for the POSIX sh).

In both the Korn and Bourne shell, $0 in the function was still the script's name, not the function name (while the $1, $2 positional parameters refer to the function arguments).

That changed in ksh93 for the function f { style of function definition where $0 becomes the function name in the function.

In zsh, $0 is the function name like in ksh93. zsh also introduced anonymous functions with either the:

function { echo $1, $2; } foo bar

or

(){ echo $1, $2; } foo bar

syntax, in which $0 becomes (anon) (or stays the script's name with set +o functionargzero like when in sh/ksh emulation).

In zsh, like in csh, the arguments of the scripts are also in the $argv array, which lifts the confusion about the program name named similarly to the arguments.

There, you can assign values to the position parameters with:

argv[1]=value

or

1=value

(and also 0=newprogramname to change the program name).

While in other Bourne-like shells, you need to use set to assign all at once:

set -- arg1 arg2

And you can't change $0.

In rc (at least the Unix port), you can't do:

1 = value

But you can do:

* = (arg1 arg2)

to set the positional parameters. You can't change $0 in rc, but you can in its derivative es with 0=newprogramname like in zsh.

TL;DR

The $1, $2... to refer to the scripts arguments comes from the Thompson shell but were not called positional parameters yet. And $0 (probably chosen in reference to argv[0] as @ikkachu well put it) refers to the script name.

The positional parameters term comes from the Bourne shell.

$0 is not a positional parameter because it doesn't refer to an argument of the script. It refers to the script's name (or the shell's argv[0] when the shell doesn't execute any script).

Related Question