6

My shell is zsh and OS is Ubuntu 13.04

I need to add directory to $PATH to make it work in following places:

  • In graphical environment(Unity) (such as startup applications, gmrun program that runs by shortcut (it's basically as "Run command" on alt+f2)
  • In terminal in Unity
  • In terminal on Ctrl + Alt + F1

I've added it in .profile and it works for first two points, but not the last. I know I may add it to .zshrc but In this case it will be wrote in tow places (violates DRY) and in the case of terminal inside unity it'll be two times in $PATH (I don't think it's very bad but it's at least not pretty)

If I add it only to .zshrc it works only for second and third cases(obviously)

What can I do?

11
  • Have you tried just adding it to .zsh and skipping .profile. Also, consider moving the last sentence to the beginning of your question. – edwin Jul 30 '13 at 16:18
  • @edwin in that case it's not in PATH for graphical apps: first in my list – RiaD Jul 30 '13 at 16:23
  • At least for the second and third cases .zsh should be enough. Try adding it to .bashrc as well. – edwin Jul 30 '13 at 16:29
  • Sure it's enough for 2nd and 3rd. Seems that bashrc isn't connected here but i'll try now – RiaD Jul 30 '13 at 16:30
  • @edwin, adding to .bashrc didn't help for first – RiaD Jul 30 '13 at 16:35
5

Setting an Environment Variable for All Logins (no matter what type)

The best way is to use ~/.pam_environment. For example, to add /opt/blah/bin to the end of your PATH, you'd put this in the .pam_environment file in your home directory:

PATH DEFAULT=${PATH}:/opt/blah/bin

Setting Environment Variables Globally (but don't do this unless you need to)

If you want to add something to the PATH for all users, use /etc/environment instead. Confusingly, /etc/environment and ~/.pam_environment don't use the same syntax. While neither is actually a script, /etc/environment looks like a script (without any export commands). So, if you wanted to add /opt/blah/bin to the end of everybody's PATH, and the PATH line in /etc/environment started out as

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"

then you would change it to:

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/blah/bin"

Analysis

Most Bourne-style shells, including bash, will source ~/.profile when they are started as a login shell. zsh is an unusual exception; it will only do this if it is invoked with the name of one of the traditional Bourne-style shells. That is, if you call zsh by the name sh or ksh (most commonly achieved by creating a symbolic link to zsh with one of those names), it will behave like them and source ~/.profile (if it is a login shell). Otherwise, it will not. (Source: man zsh.)

This is why zsh in a virtual console session (for example, where you go to Ctrl+Alt+F1) doesn't set the PATH environment variable from ~/.profile. It is a login shell, but zsh is special; it does not behave like traditional Bourne-style shells unless it is pretending to be one.

Why does zsh running a Terminal window have the environment variables you set in ~/.profile? Because they were already set in your graphical session before you launched the Terminal. When you log in graphically, the display manager (which provides the graphical login screen and manages graphical sessions) acts like a login shell. Usually it sources ~/.profile (though it is not guaranteed that it will do so, and occasionally someone changes desktop environments only to find that ~/.profile is no longer sourced when they log in graphically).

There is nothing about text-based virtual consoles that makes ~/.profile not get sourced. For example, if your shell were bash instead of zsh and you logged on in a virtual console, ~/.profile would be sourced. The issue is that, unlike the reliable behavior of a traditional Bourne-style shell, and the not quite as reliable behavior of a display manager starting a graphical login session, zsh does not source ~/.profile when it's your login shell.

Similarly, with environment variables set in ~/.profile, if you were to log in remotely (for example, by enabling SSH and logging in that way), ~/.profile would not be sourced.

This applies to the global file /etc/profile too, by the way. If you set environment variables globally there, you'd experience the same behavior you're seeing setting environment variables for your user in ~/.profile.

The solution, provided that you don't need to write a scripted test to determine the contents of your environment variables, is to set user-specific environment variables in ~/.pam_environment and systemwide (i.e., for-all-users) environment variables in /etc/environment.

When you do that, PAM (specifically, pam_env.so) sets the variables on login, for basically every type of login, and does so before the login shell (e.g., zsh, bash for most people) or login-shell-like thing (e.g., a display manager) sources its own login configuration files. This is the generally recommended way to set environment variables on Ubuntu these days.

This way solves the problem of some login shells not always sourcing ~/.profile and /etc/profile (which is the problem you're experiencing). It also solves the occasional problem of a display manager not sourcing that file as it initializes a graphical login session (this is a problem which you are not experiencing).

Alternatives

What if:

  • you need to set environment variables on login, based on the results of scripted tests? Or
  • you just don't want to use .pam_environment (or for systemwide variables, /etc/environment?

If you weren't using zsh, but instead used bash or another more traditional Bourne-style shell, then you could just set your environment variables in ~/.profile (or /etc/profile for systemwide variables). On occasion there is a configuration where this doesn't set them for graphical login sessions, but usually it works.

Setting them in ~/.bashrc will not work for this purpose. Essentially only bash sources that file, so it will work neither when zsh is your login shell nor when the display manager acts as your login shell. (In other words, in your situation that will never work at all.)

Thus, if you need a script that is sourced for all types of login, and ~/.profile is being sourced for your graphical sessions, you can simply:

  • modify zsh's configuration to source ~/.profile, or
  • make both zsh's configuration and ~/.profile source a third, shared file. (This could even be added to a separate configuration file for graphical sessions, such as .xsession, if that turned out to be needed later.)

Of those two options, the second one is better unless you've read through the contents of ~/.profileand made sure they--and the contents of any script sourced from.profile--won't cause problems if sourced byzsh`. (It usually should not, but you never know.)

The best configuration file to modify, to make zsh source ~/.profile (or some other script) on login, is ~/.zprofile. This corresponds to ~/.profile on more traditional Bourne-style shells. (Strictly speaking, it's $ZDOTDIR/.zprofile, but $ZDOTDIR is typically ~.)

You would add the line source $HOME/.profile to that file.

I emphasize, however, that if just need to perform simple assignment to environment variables (including recursive assignment where an environment variables is assigned an expression containing itself and/or other environment variables), you should just use ~/.pam_environment as explained above (or /etc/environment for systemwide environment variables).

10
  • I've tried with .pam_environment. It works on terminal in Unity but not virtual console session(path just didn't appended) – RiaD Jul 30 '13 at 17:23
  • @RiaD What does? Are you saying .pam_environment is not affecting your environment variables in a virtual console, either? Are you starting a new virtual console session (i.e., logging out and back in)? If that is the case, then the most likely explanation is that variables are set from .pam_environment, but then one of your zsh configuration files replaces PATH with its own PATH, trashing them. The solution is to make any setting of PATH in zsh's configuration files recursive. If you're unsure if this is happening, please post .zshenv, .zprofile, .zlogin and .zshrc. – Eliah Kagan Jul 30 '13 at 17:26
  • @RiaD Sorry, I don't understand what you mean. – Eliah Kagan Jul 30 '13 at 17:30
  • They aren't set(after reboot). It is not about $PATH only PAPERSIZE=a4 in this file and echo $PATH prints a4 in unity termonal but nothing in tty – RiaD Jul 30 '13 at 17:32
  • What do you mean when you say "It is not about $PATH"? What does PAPERSIZE have to do with this? You can set that variable in .pam_environment, but both your question (this one) and the post I linked to about .pam_environment are about PATH and not about PAPERSIZE. Are you saying that setting PAPERSIZE=a4 causes PATH to contain a4? What variable or variables are you setting, and how? What's the contents of your .pam_environment file? What are the contents of the four zsh configuration files I mentioned? – Eliah Kagan Jul 30 '13 at 17:35
1

First of all, you could just source your .profile in your .zshrc file.

Besides that, as you're using zsh, you can add the following to your .zshrc:

typeset -U path

# If you want it at the front of your path
path=({/custom/path/bin "${path[@]}")

# If you want it at the end of your path
path+=(/custom/path/bin)

How this works:

In zsh, the $PATH variable is bound to the $path variable; $path is an array, and $PATH is a scalar with the elements of $path joined by : (identical to ${(j|:|)path}). typeset -U path makes the elements of the path array (and thus the $PATH) unique.

   typeset [ {+|-}AEFHUafghklprtuxmz ] [ -LRZi [ n ]] [ name[=value] ... ]
   typeset -T [ {+|-}Urux ] [ -LRZ [ n ]] SCALAR[=value] array [ sep ]
          Set or display attributes and values for shell parameters.
          (...)
          -U     For  arrays  (but  not for associative arrays), keep only
                 the first occurrence of each duplicated value.  This  may
                 also  be  set for colon-separated special parameters like
                 PATH or FIGNORE, etc.  This flag has a different  meaning
                 when used with -f; see below.
1

This answer is basically based on Eliah Kagan's one and contains what I really did.

I've added to ~/.pam_environment

PATH DEFAULT=${PATH}:/home/riad/scripts

But at least on my PC it was not parsed on tty1 (Ctrl+Alt+F1) login but was parsed on graphical login. (Even locale settings that are created by unity wasn't work in non-graphical login)

The reason is that there was following line in pam configuration file for lightdm (/etc/pam.d/lightdm) :

session required        pam_env.so readenv=1 user_readenv=1 envfile=/etc/default/locale

I've added same line to /etc/pam.d/login between

@include common-session

and

@include common-password

Be careful! Bad .pam_environment file may broke your login.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.