Bash -c with positional parameters

Usually, $0 in a script is set to the name of the script, or to whatever it was invoked as (including the path). However, if I use bash with the -c option, $0 is set to the first of the arguments passed after the command string:

bash -c 'echo $0' foo bar 
# foo 

In effect, it seems like positional parameters have been shifted, but including $0. However shift in the command string doesn’t affect $0 (as normal):

bash -c 'echo $0; shift; echo $0' foo bar
# foo
# foo

Why this apparently odd behaviour for command strings?
Note that I am looking for the reason, the rationale, behind implementing such odd behaviour.

One could speculate that such a command string wouldn’t need the $0 parameter as usually defined, so for economy it is also used for normal arguments. However, in that case the behaviour of shift is odd. Another possibility is that $0 is used to define the behaviour of programs (a la bash called as sh or vim called as vi), but that cannot be, since $0 here is only seen in the command string and not by programs called within it. I cannot think of any other uses for $0, so I am at a loss to explain this.

Asked By: muru


This behaviour is defined by POSIX:

sh -c command_name [argument…]

Read commands from the command_string operand. Set the value of special parameter 0 (see Special Parameters) from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands.

As for why you’d want that behaviour: this smooths out the gap between a script and a -c string. You can directly convert between the two without any change of behaviour. Other areas rely these being identical.

It’s also in line with how program arguments work in general: this ultimately comes down to calling one of the exec functions, in which the first provided argument is also $0, and equally commonly that argument is the same as the executable you’re running. Sometimes, though, you want a special value there, and there’d just be no other way to get it. Given that the argument exists, it has to map to something, and the user needs to be able to set what that is.

This consistency (and likely historical accident) leads to the situation you find.

Answered By: Michael Homer

That gives you an opportunity to set/choose $0 when using an inline script. Otherwise, $0 would just be bash.

Then you can do for instance:

$ echo foo > foo
$ bash -c 'wc -c < "${1?}"' getlength foo
$ rm -f bar
$ bash -c 'wc -c < "${1?}"' getlength bar
getlength: bar: No such file or directory
$ bash -c 'wc -c < "${1?}"' getlength
getlength: 1: parameter not set

Not all shells used to do that. The Bourne shell did. The Korn (and Almquist) shell chose to have the first parameter go to $1 instead. POSIX eventually went for the Bourne way, so ksh and ash derivatives reverted to that later (more on that at That meant that for a long time for sh (which depending on the system was based on the Bourne, Almquist or Korn shell), you didn’t know whether the first argument went into $0 or $1, so for portability, you had to do things like:

sh -c 'echo foo in "$1"' foo foo


sh -c 'shift "$2"; echo txt files are "$@"' tentative-arg0 3 2 *.txt

Thankfully, POSIX has specified the new behavior where the first argument goes in $0, so we can now portably do:

sh -c 'echo txt files are "$@"' meaningful-arg0-for-error *.txt
Answered By: Stéphane Chazelas
Categories: Answers Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.