Bash – Using Bash -c with Positional Parameters

bash

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.

Best Answer

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
4
$ 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 http://www.in-ulm.de/~mascheck/various/find/#shell). 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

Or:

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