Is there a more comprehensive output command that does more than
printenv prints only environment variables, which may be considered an advantage. But if you want to print shell variables as well, use
echo "$x" (or
printf '%s\n' "$x", which is more robust) instead of
steeldriver's explanation of these issues is useful and correct, but I'm presenting the topic in another way here.
printenv is an external command--not built into your shell, but a separate program from your shell. It shows its own environment variables, which are those it inherits from the shell you use to run it. However, shells don't pass all their variables into their subprocesses' environments. Instead they maintain a distinction between which variables are environment variables and which are not. (Those that are not are often call shell variables.)
To see how this works, try these commands, which are enclosed in
) so they act independently1 of one another. Individually, each of these commands works the same when you run it without the
), but variables you create in earlier commands would still exist in later commands. Running the commands in subshells prevents this.
Creating a new variable, then running an external command, does not pass the variable into the command's environment. Except in the unusual case that you already have an environment variable
x, this command produces no output:
(x=foo; printenv x)
The variable is assigned in the shell, though. This command outputs
(x=foo; echo "$x")
The shell supports syntax to pass a variable into a command's environment without affecting the current shell's environment. This outputs
x=foo printenv x
(That works in a subshell, too, of course--
(x=foo printenv x)--but I've shown it without the
) because when you use that syntax, nothing is set for your current shell, so using a subshell is unnecessary to prevent subsequent commands from being affected.)
foo, then prints
(x=bar; x=foo printenv x; echo "$x")
When you export a variable, it is automatically passed into the environments of all subsequent external commands run from the same shell. The
export command does this. You can use it before you define the variable, after you define it, or you can even define the variable in the
export command itself. All these print
(x=foo; export x; printenv x)
(export x; x=foo; printenv x)
(export x=foo; printenv x)
There is no
unexport command. Even though you can export a variable before setting it, unsetting a variable also unexports it, which is to say that this prints nothing, rather than printing
(x=foo; export x; unset x; x=bar; printenv x)
But changing the value of a variable after exporting it does affect the exported value. This prints
(export x=foo; printenv x; x=bar; printenv x)
Like other processes, your shell itself inherits environment variables from its parent process. Such variables are present initially in your shell's environment and they are automatically exported--or remain exported, if you choose to think of it that way. This prints
VAR=val cmd runs
VAR set to
val in its environment):
x=foo bash -c 'printenv x'
Variables set in child processes do not affect the parent process, even if they are exported. This prints
(x=foo; bash -c 'export x=bar'; echo "$x")
A subshell is also a child process2; this also prints
(x=foo; (export x=bar); echo "$x")
That should make clearer why I've enclosed most of these commands in
) to run them in subshells.
Subshells are special, though. Unlike other subprocesses, such as those created when you run an external command like
bash, a subshell inherits most of its parent shell's state. In particular, subshells inherit even variables that aren't exported. Just as
(x=foo; echo "$x") prints
foo, so does
(x=foo; (echo "$x")).
The unexported variable is still not exported in the subshell--unless you export it--so, just as
(x=foo; printenv x) prints nothing, so does
(x=foo; (printenv x)).
A subshell is a special kind of subprocess that is a shell. Not all subprocesses that are shells are subshells. The shell created by running
bash is not a subshell and it does not inherit unexported variables. So this command prints a blank line (because
echo prints a newline even when called with an empty argument):
(x=foo; bash -c 'echo "$x"')
PS1 isn't an environment variable (and usually shouldn't be one)
Finally, as for why prompt variables like
PS1 are shell variables but not environment variables, the reasons are:
- They're only needed in the shell, not other programs.
- They are set for each interactive shell, and noninterative shells don't need them at all. That is, they don't need to be inherited.
- Attempting to pass
PS1 to a new shell would typically fail, because the shell usually resets
Point #3 deserves a bit more explanation, though if you never attempt to make
PS1 an environment variable, then you probably don't really need to know the details.
When Bash starts noninteractively, it unsets
When a noninteractive Bash shell starts up, it always3 unsets
PS1. This prints a blank line (not
PS1=foo bash -c 'echo "$PS1"'
To verify that it is actually unset, and not just set but empty, you can run this, which prints
PS1=foo bash -c 'if [[ -v PS1 ]]; then echo set; else echo unset; fi'
To verify that this is independent of other startup behavior, you could try passing any combination of
-c, or setting
BASH_ENV to the path of some script (e.g.,
BASH_ENV=~/.bashrc PS1=foo bash ...), or
ENV if you passed
--posix. In no case does a noninteractive Bash shell fail to unset
What this means is that if you export
PS1 and run a non-interactive shell which itself runs an interactive shell, it won't set have the
PS1 value you originally set. For this reason--and also because other shells besides Bash (like Ksh) don't all behave the same way, and the way you write
PS1 for Bash does not always work for those shells--I recommend against attempting to make
PS1 an environment variable. Just edit
~/.bashrc to set whatever prompt you want.
When Bash starts interactively, it often sets or resets
Conversely, if you unset
PS1 and run an interactive Bash shell, even if you prevent it from running commands from startup scripts by passing
--norc, it will still automatically set
PS1 to a default value. Running
env -u PS1 bash --norc gives you an interactive Bash shell with
PS1 set to
\s-\v\$ . Since Bash expands
\s to the name of the shell and
\v to the version number, this shows
bash-4.3$ as the prompt on Ubuntu 16.04 LTS. Note that setting
PS1's value as the empty string is not the same as unsetting it. As explained below, running
PS1= bash gives you an interactive shell with strange startup behavior. You should avoid exporting
PS1 when it is set to the empty string, in practical use, unless you understand and want that behavior.
However, if you set
PS1 and run an interactive Bash shell--and it doesn't get unset by an intermediary noninteractive shell--it will keep that value... until a startup script like the global
/etc/profile (for login shells) or
/etc/bash.bashrc, or your per-user
~/.bash_profile (all for login shells) or
~/.bashrc resets it.
Even if you edit those files to prevent them from setting
PS1--which, in the case of
/etc/bash.bashrc, I recommend against doing anyway, since they affect all users--you can't really rely on this. As mentioned above, interactive shells started from noninteractive shells won't have
PS1, unless you were to reset and reexport it in the noninteractive shell. Furthermore, you should think twice before doing that, because it is common for shell code (including shell functions you may have defined) to check
PS1 to determine whether the shell it's running in is interactive or noninteractive.
PS1 is a common way to determine if the current shell is interactive.
This is why it is so important for noninteractive Bash shells4 to unset
PS1 automatically. As section 6.3.2 Is this Shell Interactive? of the Bash reference manual says:
[S]tartup scripts may examine the variable
PS1; it is unset in non-interactive shells, and set in interactive shells.
To see how this works, see the example there. Or check out the the real-world uses in Ubuntu. By default,
/etc/profile in Ubuntu includes:
if [ "$PS1" ]; then
if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then
# The file bash.bashrc already sets the default PS1.
# PS1='\h:\w\$ '
if [ -f /etc/bash.bashrc ]; then
if [ "`id -u`" -eq 0 ]; then
/etc/bash.bashrc, which should do nothing at all when the shell is noninteractive, has:
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
Subtleties of different methods of checking for interactivity:
To achieve the same goal,
/etc/skel/.bashrc, which is copied into users' home directories when their accounts are created (so your
~/.bashrc is probably similar), has:
# If not running interactively, don't do anything
case $- in
That's the other common way to check if a shell is interactive: see if the text obtained by expanding the special parameter
- (by writing
$-) contains the letter
i. Usually this has exactly the same effect. Suppose, however, that you have not modified the code shown above that appears by default in Bash's startup scripts in Ubuntu, and that:
- you export
PS1 as an environment variable, and
- it is set, but to the empty value, and
- you start an interactive Bash shell...
/etc/profile (if it is a login shell) or
/etc/bash.bashrc won't run the commands they usually run for interactive shells.
~/.bashrc still will.
If you want to check if a shell is interactive by using
PS1 and get the right answer even when
PS1 is set but empty, you can use
[[ -v PS1 ]] or
[ -v PS1 ]/
test -v PS1 instead. Note, however, that the
[[ keyword, and the
-v test of the
test shell builtins, are particular to Bash. Not all other Bourne-style shells accept them. So you should not use them in scripts like
/etc/profile that might run in other shells (or by a display manager when you log in graphically), unless you have something else in the script that checks what shell is running and only executes Bash-specific commands when that shell is Bash (for example, by checking
1 This article explains subshells in detail. 22.214.171.124 Grouping Commands of the Bash reference manual explains the
2 Note that there are circumstances under which commands run in subshells even with the
) syntax is not used. For example, when you have commands separated by
| in a pipeline, Bash runs each of them in a subshell (unless the
lastpipe shell option is set).
3 Except for subshells. Arguably that is not even an exception, since subshells don't "start up" in the usual sense that we mean when we talk about that. (They don't really have significant initialization behavior.) Note that when you run
bash--with or without arguments--inside a Bash shell, that creates a subprocess that is a shell, but it is not a subshell.
4 Note that not all shells--not even all Bourne-style shells--behave this way. But Bash does, and it is very common for Bash code, including code in startup scripts, to rely on it.