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.

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?

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?

It's a shell variable, and it's passed to the command's environment; it's not necessarily exported to the shell's environment. export is a builtin, but if you use printenv _, it will show you how it was invoked: /usr/bin/printenv on this system.
– Toby SpeightApr 10 '18 at 10:50

Note that bash -c export | grep _= (from Bash), will show how the parent shell invoked the bash command, even though $_ is unset in the parent.
– Toby SpeightApr 10 '18 at 10:52

3 Answers
3

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.

Somewhat confusingly, _ would also be a valid name for a variable, unlike the names of the other special parameters. At least Bash 4.4 allows assignments to it, without complaints. It's just not useful because the special effect immediately overrides the value.

Not every shell variable es marked as exported as you can see in the output of declare -p.

It does not make any sense for bash to mark $_ as exported because it automatically adds this variable to the environment of child processes but with a different value than the one it has in the shell (at that moment).

Showing it as exported would just confuse the user about what is going to happen with the environment of external commands.