bash - Why does subshell not inherit exported variable (PS1)?
2014-07
I am using startx
to start the graphical environment. I have a very simple .xinitrc
which I will add things to as I set up the environment, but for now it is as follows:
catwm & # Just a basic window manager, for testing.
xterm
The reason I background the WM and foreground terminal and not the other way around as often is done, is because I would like to be able to come back to the virtual text console after typing exit
in xterm
. This appears to work as described.
The problem is that the PS1
variable that currently is set to my preference in /etc/profile.d/user.sh
(which is sourced from /etc/profile
supplied by distro), does not appear to propagate to the environment of the xterm
mentioned above. The relevant process tree is as follows:
\_ -bash
\_ xinit /home/user/.xinitrc -- /etc/X11/xinit/xserverrc :0 -auth /tmp/serverauth.ggJna3I0vx
\_ /usr/bin/X -nolisten tcp :0 -auth /tmp/serverauth.ggJna3I0vx vt1
\_ sh /home/user/.xinitrc
\_ /home/user/catwm
\_ xterm
\_ bash
The shell started by xterm
appears to be interactive, the shell executing .xinitrc
however is not. I am ok with both, the assumptions about interactivity seem to be perfectly valid, but now I have a non-interactive shell that spawns an interactive shell indirectly, and the interactive shell has no chance to automatically inherit the prompt, because the prompt was unset or otherwise made unavailable higher up the process tree. How do I go about getting my prompt back?
Commands env
and export
list only variables which are exported. $PS1
is usually not exported. Try echo $PS1
in your shell to see actual value of $PS1
.
Non-interactive shells usually do not have $PS1
. Non-interactive bash
explicitly unsets $PS1
.1 You can check if bash
is interactive by echo $-
. If the output contains i
then it is interactive. You can explicitly start interactive shell by using the option on the command line: bash -i
. Shell started with -c
is not interactive.
The /etc/profile
script is read for a login shell. You can start the shell as a login shell by: bash -l
.
With bash
shell the scripts /etc/bash.bashrc
and ~/.bashrc
are usually used to set $PS1
. Those scripts are sourced when interactive non-login shell is started. It is your case in the xterm
. See Setting the PS? Strings Permanently
Possible solutions
- Start the shell inside
xterm
as a login shell:bash -l
. Check if/etc/profile
and~/.profile
do not contain code which should be executed only after login. Maybe slight modifications of the scripts will be needed. - Use a different shell. For example
dash
does not unset$PS1
. You can use such a shell just as the non-interactive shell which will run the scripts up toxterm
. - Give up the strict POSIX compliance and use the bash-standard place for setting
$PS1
:/etc/bash.bashrc
or~/.bashrc
. - Give up the strict POSIX compliance and source your own startup script like:
bash --rcfile <(echo "PS1=$PS1save") -i
- Start the intermediate shells from
startx
tillxterm
as interactive shells (bash -i
). Unfortunately this can have some side-effect and I would not do this.
I have a shell script to set up some environment variables and launch whatever program I send in as an argument:
export PATH=$HOME/local/bin:$PATH
export LD_LIBRARY_PATH=$HOME/local/lib:$LD_LIBRARY_PATH
export TESTER="MY TEST VAR"
$@
When I use this to call bash
for example it works:
kjfletch@flatbed:~$ envrun.sh bash
kjfletch@flatbed:~$ echo $LD_LIBRARY_PATH
/home/kjfletch/local/lib:
kjfletch@flatbed:~$ echo $TESTER
MY TEST VAR
When I use it to call a terminal (xterm
, aterm
, ...) my LD_LIBRARY_PATH
gets unset:
kjfletch@flatbed:~$ echo $LD_LIBRARY_PATH
kjfletch@flatbed:~$ echo $TESTER
MY TEST VAR
Why does this happen? How can I stop this? (I am running Debian 5.0)
Update
My terminal is not calling bash as a login:
kjfletch@flatbed:~$ echo $0
bash
My LD_LIBRARY_PATH
does not show up in any of the bash startup files (apart from .bash_history and ~/.profile does not exist.):
kjfletch@flatbed:~$ grep "LD" ~/.bash*
kjfletch@flatbed:~$ grep "LD" /etc/bash.bashrc
kjfletch@flatbed:~$ grep "LD" /etc/profile
In the terminal (xterm, aterm etc), check how the shell was invoked: A login shell will show "-bash" and a non-login shell will show "bash" when you call echo $0
.
$ echo $0
-bash
$ bash
$ echo $0
bash
A login bash shell will read the following in order:
- /etc/profile
- ~/.bash_profile
- ~/.bash_login
- ~/.profile
Check if any of these files exist, and if they reset the variable. You'll also have to follow any files these files include.
If bash is not invoked as a login shell, it will still read the below files if is determined to be an interactive shell.
- /etc/bash.bashrc
- ~/.bashrc
A simple way to determine the kind of bash shell being invoked is to define your .bash_profile and .bashrc, and echo "Login shell" and "Interactive shell" respectively.
Once you know the kind of shell being invoked, you have the option of adding your script to the .bashrc or .bash_profile file in your home directory. Alternatively, you can disable the reset of LD_LIBRARY_PATH.
Note that if your .bashrc or .bash_profile is protected by a guard similar to the one below, you might have to call your script outside of it:
if [ "X$BASH_SOURCED" != "XYES" ]; then
export BASH_SOURCED=YES
fi
Such guards are normally placed to prevent a script being sourced multiple times in a session.
Edit: If it is proving tedius to track down where the variable is being reset, and you have access to /etc/profile or /etc/bash.bashrc for example, you can temporarily add "set -x" near the top of the script to see all the commands that get executed. The output will be pretty verbose, so first do a "set -x" in your shell and run a few commands so you know what to expect.
bash will use different start-up scripts depending on how it is started. There are seven different ways to start it, but the most important are login shells versus non-login interactive shells.
Check out the bash manual for more details. I would suspect the /etc/profile or the ~/.bash_profile is doing something to reset the LD_LIBRARY_PATH variable.
Edit: I think you have done all you reasonably can to show that bash does not have any startup script that unsets LD_LIBRARY_PATH. It is time to bring out the big guns.
The following command will display the entire environment as each process starts up, from bash to xterm, and anything else that happens to be involved -- you will probably get a large amount of output, so saving the output to a file is a good idea.
strace -v -f -e trace=process -o strace_output.txt envrun.sh xterm
Now, the file *strace_output.txt* will show each system call made by your script and every child process, and you will be able to see which process was the last to have LD_LIBRARY_PATH before it got removed.
The terminal binary is most likely setgid
to group utmp
. Setuid and setgid binaries unset LD_LIBRARY_PATH
for security reasons; see ld.so(8)
:
The necessary shared libraries needed by the program are searched for in the following order
- Using the environment variable
LD_LIBRARY_PATH
(LD_AOUT_LIBRARY_PATH
for a.out programs). Except if the executable is a setuid/setgid binary, in which case it is ignored.
(This question is very old, but I just encountered the same problem, and am documenting the solution for posteriority:)
I've had this problem with GNU screen (the terminal multiplexer), but it can as well happen with a regular terminal. Teddy was right in my case, screen has setguid set.
$ ls -l /usr/bin/screen
-rwxr-sr-x 1 root 84 361016 Mar 30 2011 /usr/bin/screen
^
My solution was to save LD_LIBRARY_PATH before execution, and restoring it afterwards. So I created a wrapper ~/bin/screen (put ~/bin on PATH), with the following contents:
#!/bin/bash
# somehow, screen resets LD_LIBRARY_PATH.
# save it here, and restore it in .bashrc
export PRESERVE_LD_LIBRARY_PATH="$LD_LIBRARY_PATH"
/usr/bin/screen "$@"
and then made it executable with chmod +x ~/bin/screen
. You might have to open a new shell to get it to pick up the wrapper.
Then I added the following to ~/.bashrc . Remember ~/.bashrc gets sourced every time you start bash, unlike ~/.bash_profile which only gets sourced at login (usually at startup, or when you login over ssh).
if [[ "$PRESERVE_LD_LIBRARY_PATH" != "" ]]; then
export LD_LIBRARY_PATH="$PRESERVE_LD_LIBRARY_PATH"
#echo "restored LD_LIBRARY_PATH"
export -n PRESERVE_LD_LIBRARY_PATH
fi
Now screen (or aterm, xterm, ... just substitute it above) should preserve $LD_LIBRARY_PATH as wanted.
It seems like you have some .bashrc (or equivalent) file in your home directory which defines this variable. I don't know much more details though.
Edit Ok, since starting bash works, I'm guessing not the .bashrc. But maybe some other configuration file which happens to be executed on the same way, when you start xterm or aterm.
Most windowing systems recreate the login process when they launch a terminal window, primarily because the terminal window becomes the child of the windowing manager not the launching shell.
So, put it in your .bash_profile or .bashrc if you want it to show up in a new window.
Another alternative is to hand xterm (for example) an argument to run a startup script. Don't exit at the end of that script....