25

Suppose I have

export MY_VAR=0

in ~/.bashrc.

I have an opened gnome terminal, and in this terminal, I change $MY_VAR value to 200. So, if I do

echo $MY_VAR

in this terminal, 200 is shown.

Now, I opened another tab in my gnome terminal, and do

echo $MY_VAR

...and instead of 200, I have 0.

What should I do to persist the 200 value when a terminal modifies an environment variable, making this modification (setting to 200) available to all subsequent sub shells and such? Is this possible?

  • Environment variables work exactly as the user in the question describes them. The only misconception here is that the second terminal is "a subsequent terminal" in terms of the propagation of environment variables. Had it been started from the command line in the first terminal, it would have worked (but the second shell would reset it to 0 again due to running the .bashrc file, but that would have been easy to fix with a conditional). – Kusalananda Apr 18 '21 at 16:32
  • Can't you just use a cron job to run a .profile script with all your exports of your ENV variable every minute or something? It's not like it's computationally expensive... – Benjamin Apr 18 '21 at 10:34

5 Answers5

27

A copy of the environment propagates to sub-shells, so this works:

$ export MY_VAR=200
$ bash
$ echo $MY_VAR
200

but since it's a copy, you can't get that value up to the parent shell — not by changing the environment, at least.

It sounds like you actually want to go a step further, which is to make something which acts like a global variable, shared by "sibling" shells initiated separately from the parent — like your new tab in Gnome Terminal.

Mostly, the answer is "you can't, because environment variables don't work that way". However, there's another answer, which is, well, you can always hack something up. One approach would be to write the value of the variable to a file, like ~/.myvar, and then include that in ~/.bashrc. Then, each new shell will start with the value read from that file.

You could go a step further — make ~/.myvar be in the format MYVAR=200, and then set PROMPT_COMMAND=source ~/.myvar, which would cause the value to be re-read every time you get a new prompt. It's still not quite a shared global variable, but it's starting to act like it. It won't activate until a prompt comes back, though, which depending on what you're trying to do could be a serious limitation.

And then, of course, the next thing is to automatically write changes to ~/.myvar. That gets a little more complicated, and I'm going to stop at this point, because really, environment variables were not meant to be an inter-shell communication mechanism, and it's better to just find another way to do it.

techraf
  • 5,941
mattdm
  • 40,245
  • 11
    I can hear the shell squealing under the torture –  Feb 28 '11 at 18:58
  • Yeah sorry. That's why I stopped. :) – mattdm Feb 28 '11 at 19:08
  • "which is to make something which acts like a global variable" - Well, indeed, that's what I was trying to acomplish. The ~/.myvar isn't pretty, but it works. Do you have another suggestion? – Somebody still uses you MS-DOS Feb 28 '11 at 19:11
  • 2
    Well, let's step back a bit. Why do you want a global variable? – mattdm Feb 28 '11 at 19:12
  • 1
    @mattdm: I have some "bureaucracies" that I have to obey. One of them is a bunch of codes I need to commit along with a comment. These codes are, now, in a folder hierarchy (home/user/projects/projectcode/code1/code2). I made a function that extracts code1 and code2 to env vars (so when I need to do an activity, I just call the function inside the dir with the codes), and them, when commiting, I use vim and there are some functions that read these vars and put in a comment automatically. I think using files for that can be a solution. – Somebody still uses you MS-DOS Feb 28 '11 at 20:24
  • Files are definitely the normal way to solve that problem. – mattdm Feb 28 '11 at 20:39
  • @mattdm: Yeah. Since it's my workstation machine only, I think I'm going to use '/tmp'. Thanks for the answer. – Somebody still uses you MS-DOS Feb 28 '11 at 20:46
  • 1
    It would be really nice if somebody made a program that, if you add it to run in your ~/.bashrc file, you can truly set system-wide variables. You would set one like syswidevar MYVAR=200, and ANY shell - new or old - can access $MYVAR and see the current value. The value could be stored in memory. I unfortunately lack the programming skills to make something like this. – leetbacoon Jan 26 '20 at 17:01
  • 1
    @leetbacoon That really can't work without major changes to how environment variables work, as explained above. – mattdm Jan 26 '20 at 19:30
  • Hey @mattdm. In case my Python script (taker.py) needs data from multiple C files (giver1.c, giver2.c, ...etc), would you still recommend this ? – Pe Dro Jan 21 '21 at 07:00
  • 1
    @Pe Dro — I wouldn't recommend this at all, really. It's meant mostly as an illustration of how environment variables work. It sounds like you have an entirely different problem that probably is its own question. – mattdm Jan 22 '21 at 13:27
14

Suppose I have export MY_VAR=0 in ~/.bashrc.

That's your mistake right there. You should define your environment variables in ~/.profile, which is read when you log in. ~/.bashrc is read each time you start a shell; when you start the inner shell, it overrides MY_VAR. If you hadn't done that, your environment variable would propagate downwards.

For more information on ~/.bashrc vs ~/.profile, see my previous posts on this topic.

Note that upward propagation (getting a modified value from the subshell automatically reflected in the parent shell) is not possible, full stop.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
5

Fish shell can do this with:

set -U MY_VAR 0

(see http://fishshell.com/docs/current/commands.html#set)

To execute a fish command from another shell you can run fish -c, e.g. :

fish -c "set -u MY_VAR 0"
rien333
  • 653
4

Don't use environment variables at all. Use files.

To prevent processes from stepping on each other when updating/reading the file, use lockfiles and small front-end updater scripts whose purpose is just updating a file with $1 if it's not locked. Lockfiles are implemented by basically checking if a specific file exists (/var/run/yourscript.lck) and if it does, wait for the file to disappear for a while, and fail if it doesn't. Also you must delete the lockfile when done updating the file.

Be prepared to handle a situation where a script can't update a file because the file is busy.

LawrenceC
  • 10,992
  • 9
    A trick: use directories instead of files for locking, because mkdir works as an atomic test-and-create. – mattdm Feb 28 '11 at 20:53
  • 1
    If you guys want to talk about locking, then you might as well be using flock(2) – Ehtesh Choudhury Apr 12 '13 at 23:20
  • @mattdm I found that a symlink ln -s dummy lockfile (even if broken) may work too as it will fail if the symlink already exists. I wonder some way to test if that is truly 100% secure. – Aquarius Power Aug 18 '13 at 20:26
  • If you use files, what if the computer is forcefully and unexpectedly powered off? Upon reboot your script may still think something is locked if a file still exists – leetbacoon Jan 26 '20 at 17:37
  • Add a line in /etc/rc.local or other script that starts on reboot to delete the lockfile. – LawrenceC Jan 26 '20 at 17:40
1

When I just have googled this question up, I was trying to solve the problem of updating state (i.e. shell variables) from out of subshells. So that one could assign variables, say, inside of a pipeline – and the assignment would be transparently visible to the parent.

Off course, this is not possible in a simple way because individual parts of a pipeline are executed in subshells, which are fork'ed from the parent shell and therefore have memory which is copy-on-write view on the parent's memory. However, I supposed that slim and transparent solution could be possible based on "shared memory" sort of IPCs.

And I have even found an implementation of exactly this design... but it's in Perl.

Adding this answer anyway as a possible solution.

ulidtko
  • 1,018
  • 8
  • 16