bash - Why does subshell not inherit exported variable (PS1)?

07
2014-07
  • amn

    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?

  • Answers
  • pabouk

    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 to xterm.
    • 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 till xterm as interactive shells (bash -i). Unfortunately this can have some side-effect and I would not do this.

  • Related Question

    linux - Why does my LD_LIBRARY_PATH get unset launching terminal?
  • kjfletch

    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
    

  • Related Answers
  • nagul

    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:

    1. /etc/profile
    2. ~/.bash_profile
    3. ~/.bash_login
    4. ~/.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.

    1. /etc/bash.bashrc
    2. ~/.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.

  • Kevin Panko

    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.

  • Teddy

    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.
  • jdm

    (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.

  • Gnoupi

    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.

  • kmarsh

    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....