Search code examples
bashmacosterminaltmux

Why does login shell launched by tmux modify the PATH in an unexpected manner?


On my macOS Sierra 10.12.6, I have my ~/.bash_profile as shown below.

macbookpro:~ lone$ echo $TERM_PROGRAM
Apple_Terminal
macbookpro:~ lone$ cat ~/.bash_profile
echo 1: PATH: $PATH
PATH=BEG:$PATH:END
echo 2: PATH: $PATH

I do not have ~/.bashrc or ~/.profile.

macbookpro:~ lone$ ls -ld ~/.bash*
-rw-------  1 lone  CORP\Domain Users   6875 Jan 12 19:05 /Users/lone/.bash_history
-rw-r--r--  1 lone  CORP\Domain Users     59 Jan 12 19:05 /Users/lone/.bash_profile
drwx------  3 lone  CORP\Domain Users    102 Jan 12 19:06 /Users/lone/.bash_sessions
macbookpro:~ lone$ ls -l ~/.profile
ls: /Users/lone/.profile: No such file or directory

When I launch a new terminal, I get this output, which is expected.

Last login: Fri Jan 12 11:02:56 on ttys000
1: PATH: /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
2: PATH: BEG:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:END
macbookpro:~ lone$ rm -rf ~/.bash_sessions/

Now if I run tmux in this terminal, I get this output, in the tmux pane after it starts.

1: PATH: /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:BEG:END
2: PATH: BEG:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:BEG:END:END
macbookpro:~ lone$

Why does the first line show BEG:END in the end? I was expecting it to have BEG in the beginning and END in the end just like in the case of launching a terminal.


Solution

  • I believe I have found the answer. The /etc/profile file looks like this.

    # System-wide .profile for sh(1)
    
    if [ -x /usr/libexec/path_helper ]; then
        eval `/usr/libexec/path_helper -s`
    fi
    
    if [ "${BASH-no}" != "no" ]; then
        [ -r /etc/bashrc ] && . /etc/bashrc
    fi
    

    The clue is this command invocation in this startup file.

    /usr/libexec/path_helper -s
    

    Let me remove ~/.bash_profile and simulate the startup sequence.

    macbookpro:~ lone$ rm ~/.bash_profile
    macbookpro:~ lone$ cat /etc/paths
    /usr/local/bin
    /usr/bin
    /bin
    /usr/sbin
    /sbin
    macbookpro:~ lone$ ls /etc/paths.d/
    macbookpro:~ lone$ /usr/libexec/path_helper -s
    PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"; export PATH;
    

    So what we see so far is that path_helper takes the paths in /etc/paths and returns a path string that /etc/profile takes and evaluates to define the PATH variable.

    Let us see what happens when we have redefined PATH variable to BEG:$PATH:END ourselves (to simulate we had done earlier in ~/.bash_profile).

    macbookpro:~ lone$ PATH=BEG:$PATH:END
    macbookpro:~ lone$ echo $PATH
    BEG:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:END
    macbookpro:~ lone$ /usr/libexec/path_helper -s
    PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:BEG:END"; export PATH;
    

    So path_helper appears to construct the PATH in the following manner:

    1. Pick all paths from /etc/paths.

      /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
      
    2. Append the current PATH.

      /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:BEG:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:END
      
    3. Cleanup the PATH by removing duplicates.

      /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:BEG::END