25

As the title says, I want to be able to change environment variables in a parent process (specifically, a shell) from a child process (typically a script).  From pseudo terminal /dev/pts/id trying to export key=value from child script, so exported variables has to be passed somehow to parent, if possible?

echoing cmd > /proc/$$/fd/0 doesn't execute cmd, only view command in shell terminal emulator, and of course using $(cmd) instead of cmd executes in subshell, and export doesn't add variables to parent process.

  1. I prefer that all the work be done in the child side.

  2. I was asked in comments, what am I trying to achieve? that is a general question, and I'm trying to use the positive answer to pass variables from a script executed (spawned) by a (parent) shell, so that the user can benefit from added variables without any further work.  For example, I would like to have a script install an application, and the application directory should be added in the parent shell path.

8
  • Setting an environment variable in a parent process is not possible from a child. Try adding a description of what it is you want to achieve with this to the question.
    – Kusalananda
    Commented Jun 22, 2017 at 8:18
  • @Kusalananda edited
    – Error
    Commented Jun 22, 2017 at 13:54
  • You can't achieve a communication between two processes (whichever their relationship might be: parent → child or not) without a mechanism on both sides: one of them to talk and the other to listen.
    – dan
    Commented Jun 27, 2017 at 6:02
  • 1
    Why is this a duplicate of unix.stackexchange.com/questions/38205/…. That answer is to change env variables in any other process, but this one is specifically for a child -> parent relationship. And if the parent calls the child in the right way (i.e. source as I answered above), then it should work. Commented Oct 21, 2020 at 21:02
  • 1
    In case anyone's curious why you can't force a change in the parent environment when your child script is called with bash, it's for security and reliability. You don't want to just any script you call to mess up your current script's functioning. If you choose to source a script, that requires a higher level of trust and understanding about what it does. Commented Oct 21, 2020 at 21:06

3 Answers 3

26

No, it's not possible

Not without some kind of workaround.

Environment variables can only be passed from parent to child (as part of environment export/inheritance), not the other way around.

8
  • any possible hacks?
    – Error
    Commented Jun 22, 2017 at 3:59
  • @Error, use temporary file to store the variable Commented Jun 22, 2017 at 5:55
  • 1
    @Error any possible hacks? There's a nasty hack: attach a debugger to the parent process and have it run setenv( "ENVAL=value" ); See the question linked in the comments above. Something like that is great when someone thinks setting an environment variable to "read-only" in bash actually works. Commented Jun 22, 2017 at 10:06
  • @RomeoNinov edited
    – Error
    Commented Jun 22, 2017 at 13:54
  • 1
    @Andry No, your comment is incorrect. That question/answer talks about changing environment variable of a certain process while it's running, whereas this question/answer talks about passing environment variable from a child process to the parent process. These are totally orthogonal subjects.
    – heemayl
    Commented Feb 22, 2019 at 17:48
9

You can transmit variables values from a child to its parent process through a file or a named pipe.

Here is a theoretical simplest example:

child process:

echo ${variable} >/tmp/file

parent process:

read variable </tmp/file
2
  • question edited
    – Error
    Commented Jun 22, 2017 at 13:58
  • 1
    alternatively echo "variable=${variable}" > /tmp/file; source /tmp/file Commented Dec 14, 2020 at 14:25
5

This is very tricky if the parent process is not expecting and cooperating with it.  In that case, see change environment of a running process and Is there a way to change another process's environment variables?

If the parent process is expecting the value and cooperating with the transfer, the simple way is to use command substitution:

export VAR=$(cmd)

This assumes that the value of the variable is the only thing the program wants to write.  If the child process needs to be able to write to the screen (specifically, the parent’s stdout), we can do that by hiding the parent’s file descriptor 1 in another file descriptor:

exec 3>&1       # Copy our file descriptor 1 into file descriptor 3.
                # child_prog will be invoked with file descriptor 1 pointing to a pipe
                # for the command substitution, but all other file descriptors intact.
                # Specifically, fd3 will point to our stdout.
export var=$(child_prog)
exec 3>&-       # (Optionally) close fd3 as cleanup.

Now, if child_prog is short and simple, it may be easiest simply to write the value for the variable to file descriptor 1 and use file descriptor 3 (cmd >&3) as the standard output.  If it’s large and/or complex, you’ll want to do something like:

exec 5>&1       # Redirect fd1 (the command substitution pipe) to fd5.
exec 1>&3       # Set our fd1 (stdout) to our parent's stdout (which was passed in as fd3).
exec 3>&-       # Close fd3; it’s no longer needed.

and then use stdout normally, and use >&5 for writing the value.

So far I’ve been assuming that you want to pass only one value to one variable.  If you have multiple values, it’s a simple matter of delimiting them with a character (or string) that’s guaranteed not to appear in any of the values.  If we select @@, then the parent can say

exec 3>&1
temp=$(child_prog)
exec 3>&-
export var1="${temp%%@@*}"
rest="${temp#*@@}"
export var2="${rest%%@@*}"
export var3="${rest#*@@}"

and the child can say echo "value1@@value2@@value3" >&5.

If it’s hard to find a string of printing characters that’s guaranteed not to appear in any of the values, you can use newline.  Just change @@ to newline in the above commands:

Parent:

export var1="${temp%%
*}"
rest="${temp#*
}"
export var2="${rest%%
*}"
export var3="${rest#*
}"

Child:

printf "%s\n" "value1" "value2" "value3" >&5


Yet another variation is to have the child feed commands back to the parent, rather than values.  If the parent says . <(child_prog), it runs the child, captures the output, and executes it.  Then the child can do

printf "export var1='value1'\nexport var2='value2' var3=\"value3\"\n" >&5

(I tested this with a value3 that contained an apostrophe, so I had to quote it with \"…\", and I left it that way just to illustrate the alternative syntax.)

A feature of this technique is that you can add variable(s) to be exported without changing the code in the parent.

This approach requires that the parent process be running bash (or maybe one of the other advanced shells?), since POSIX doesn’t support <(cmd).

6
  • 1
    you could use eval as well
    – hbogert
    Commented Aug 17, 2020 at 12:35
  • @hbogert: And you could fly a kite during a thunderstorm, but why would you want to? Seriously, can you describe a realistic, practical example of something you could do with eval that you can’t do with my answer as written?  eval is like a chainsaw; it is good for some things, but you should think long and hard before you use it. Commented Aug 17, 2020 at 12:56
  • 1
    Yes, as you said, <() is a bash feature, so in the case you are not running Bash, you can resort to eval. Further, sourcing a dynamically generated script is pretty much eval.
    – hbogert
    Commented Aug 17, 2020 at 13:04
  • Hey, how do you know that child_prog will be invoked with file descriptor 1 pointing to a pipe?
    – clapas
    Commented Aug 19, 2020 at 12:55
  • 1
    Nevermind. I found out myself with this command: echo "$(ls -l /proc/self/fd)". I can see fd 1 points to a pipe.
    – clapas
    Commented Aug 20, 2020 at 9:42

Not the answer you're looking for? Browse other questions tagged .