When is `_` an Environment Variable in Bash Shell?

bashenvironment-variables

Bash Manual says (manpage, my emphasis):

When Bash invokes an external command, the variable $_ is set to the full pathname
of the command and passed to that command in its environment.

And (Special Parameters):

_

($_ , an underscore.) At shell startup, set to the absolute
pathname used to invoke the shell or shell script being executed as passed in the environment
or argument list. Subsequently, expands to the last argument to the previous
command, after expansion. Also set to the full pathname used to invoke each command executed and placed in the environment exported to that command.
When checking mail, this parameter holds the name of the mail file.

  1. In a bash shell, I run:

    $ bash
    $ export | grep '_=' 
    

    According to the manual, _ should be an environment variable of
    the new bash shell. export is supposed to output all the
    environment variables of the new bash shell, but it doesn't output
    _. So I wonder whether _ is an environment variable of the new
    bash shell?

  2. Actually in any bash shell, the same thing happens

    $ export | grep '_='
    

    doesn't output anything. So I wonder if _ is ever an environment
    variable of a bash shell?

  3. For comparison:

    $ dash
    $ export  | grep '_='        
    export _='/bin/dash'
    

My post is inspired by Mike's comment and Stephane's reply.

Best Answer

Yes, _ is an environment variable of the new Bash shell; you can see that by running

tr '\0' '\n' < /proc/$$/environ | grep _=

inside the shell: that shows the contents of the shell’s initial environment. You won’t see it in the first shell because there wasn’t a previous shell to set it before it started.

Expanding $_ inside Bash refers to the _ special parameter, which expands to the last argument of the previous command. (Internally Bash handles this by using a _ shell variable, which is updated every time a command is parsed, but that’s really an implementation detail. It is “unexported” every time a command is parsed.) export doesn’t show _ because it isn’t a variable which is marked as exported; you can however see it in the output of set.

In the first example, the new Bash shell parses and executes the commands in its startup files, so when running explore | grep '-=', _ has already been overwritten and marked as not exported.

In the dash example, it doesn't seem to execute any start-up file, so you’re seeing the variable as an environment variable that was set by Bash before running dash.