28

I know that system wide environment variables can be set by adding entries into

/etc/environment

or

/etc/profile

But that requires system restart or X restart.

Is it possible to set an environment variable in Ubuntu / Linux so that immediately available system wide without restarting OS or logging out the user?

Abhijeet Pathak
  • 1,948
  • 3
  • 20
  • 28
  • X restart is enough, why would you want to do a system restart? – fge Dec 30 '11 at 10:42
  • Well, I don't want to do a system restart. Thats the whole point. – Abhijeet Pathak Dec 31 '11 at 12:43
  • 1
    This means you cannot do what you ask, then: you will _at least_ have to restart X. You cannot change the environment of an already running process. – fge Dec 31 '11 at 12:45
  • 1
    @fge actually, you can but it requires you to attach to the process with `gdb`. It works but it's very hackish – SiegeX Aug 11 '12 at 03:39

3 Answers3

18

The simple answer is: you cannot do this in general.

Why can there be no general solution?

The "why?" needs a more detailed explanation. In Linux, the environment is process-specific. Each process environment is stored in a special memory area allocated exclusively for this process.

As an aside: To quickly inspect the environment of a process, have a look at /proc/<pid>/env (or try /proc/self/env for the environment of the currently running process, such as your shell).

When a ("parent") process starts another ("child") process (via fork(2)), the environment the environment of the parent is copied to produce the environment of the child. There is no inheritance-style association between those two environments thereafter, they are completely separate. So there is no "global" or "master" environment we could change, to achieve what you want.

Why not simply change the per-process environment of all running processes? The memory area for the environment is in a well-defined location (basically right before the memory allocated for the stack), so you can't easily extend it, without corrupting other critical memory areas of the process.

Possible half-solutions for special cases

That said, one can imagine several special cases where you could indeed achieve what you want.

  • Most obviously, if you do "size-neutral" changes, you could conceivable patch up all environments of all processes. For example, replace every USER=foo environment variable (if present), with USER=bar. A rather special case, I fear.

  • If you don't really need to change the environments of all processes, but only of a class of well-known ones, more creative approaches might be possible. Vorsprung's answer is an impressive demonstration of doing exactly this with only Bash processes.

There are probably many other special cases, where there is a possible solution. But as explained above: no solution for the general case.

Community
  • 1
  • 1
earl
  • 40,327
  • 6
  • 58
  • 59
14

This perl program uses gdb to change the USER variable in all currently running bash shells to whatever is given as the program arg. To set a new variable the internal bash call "set_if_not" could be used

#!/usr/bin/perl

use strict;
use warnings;

my @pids = qx(ps -C bash -o pid=);
my $foo  = $ARGV[0];
print "changing user to $foo";
print @pids;

open( my $gdb, "|gdb" ) || die "$! gdb";
select($gdb);
$|++;
for my $pid ( @pids ) {
    print "attach $pid\n";
    sleep 1;
    print 'call bind_variable("USER","' . $foo . '",0)' . "\n";
    sleep 1;
    print "detach\n";
}

This only works with bash ( I only tested it with version 4.1 on Ubuntu 10.04 LTS) and does not alter the environment for arbitrary already running programs. Obviously it must be run as root.

Vorsprung
  • 32,923
  • 5
  • 39
  • 63
  • Why root is needed for this? – osgx May 09 '13 at 15:39
  • 2
    @osgx the implication is that the variable must be changed in all shells running on the system. To alter processes for all users, the obvious thing to do is to be root – Vorsprung May 09 '13 at 15:54
  • Definitely +1! It could be simplified a little bit. `ps -C bash -o pid=` could be used to eliminate the `PID` header row. Then `@pid` could be used instead of `@pids[ 1 .. $#pids ]`. – TrueY May 10 '13 at 09:34
  • @TrueY or I could ``shift(@pids)`` tmtowtdi :) – Vorsprung May 10 '13 at 09:54
  • Sure! But not to write the header file is more effective. And it takes only one extra `=`. – TrueY May 10 '13 at 10:23
  • @TrueY also, it makes the program slightly more comprehensible to non perl speakers. I've changed it, thanks for the suggestion – Vorsprung May 10 '13 at 11:11
  • I tried to find some man pages about `bind_variable`, w/o luck. I `objdump`'d bash and I found `bind_variable` and also `set_if_not`. Is there some place to find description or only in the source of bash? – TrueY May 10 '13 at 11:11
  • @TrueY I downloaded the source (from http://ftp.gnu.org/gnu/bash/bash-4.1.tar.gz ) All the relevant stuff for this question - on setting environment variables - seems to be in variables.c – Vorsprung May 10 '13 at 11:14
  • Thanks! So You get if from the source. Anyway nice solution! – TrueY May 10 '13 at 11:18
  • Nice hack! One thing to note: this currently only sets the variable locally, that is, the variable is not exported. – earl May 11 '13 at 10:45
  • @earl could you give an example of the USER *not* being exported? Launching a new shell etc shows the USER as whatever it is set to. Maybe you have a more sophisticated understanding of how this works than I do? Thanks – Vorsprung May 11 '13 at 14:46
  • @Vorsprung No deep understanding, sorry. It's just what I assumed from skiming through the bash source code. To try it out, I attached to an existing bash, called `bind_variable` to create a new `FOO=bar` binding, and then forked a new shell from this existing bash. FOO was not set in this new child. bash 4.2.45. Did not try with USER, though; quite possible that it works for already exported variables. – earl May 12 '13 at 23:34
2

I fear the solution here is a frustrating one: Don't use environment variables. Instead use a file.

So, instead of setting up /etc/environment with:

SPECIAL_VAR='some/path/I/want/later/'

And calling it with:

$SPECIAL_VAR

Instead, create a file at ~/.yourvars with the content:

SPECIAL_VAR='some/path/I/want/later/'

And source the thing every time you need the variable:

cd `source ~/.yourvars; echo $SPECIAL_VAR`

A hack? Perhaps. But it works.

mlissner
  • 17,359
  • 18
  • 106
  • 169