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.

Error
  • 391
  • 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 Jun 22 '17 at 08:18
  • @Kusalananda edited – Error Jun 22 '17 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 Jun 27 '17 at 06:02
  • Anwered here for both Linux and Windows: https://unix.stackexchange.com/questions/38205/change-environment-of-a-running-process/502129#502129 The idea is to pass into child script a temporary directory path, have save files into it and load back in a parent script using introduced help scripts. – Andry Feb 23 '19 at 15:36
  • Calling the child with the source command would work (to set variables in the parent), right? E.g. put source child.sh in your parent.sh. https://linuxize.com/post/bash-source-command/ It technically handles it in the scripts, not the environments, but I think it does what the OP wanted. – combinatorist Oct 21 '20 at 20:53
  • 1
    Why is this a duplicate of https://unix.stackexchange.com/questions/38205/change-environment-of-a-running-process. 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. – combinatorist Oct 21 '20 at 21:02
  • I think the key issue here is that the OP is looking for a way to do this without any work on the parent side. My suggestion just asks the user of the parent to call the script differently (with source instead of say, bash), which might count as work. – combinatorist Oct 21 '20 at 21:03
  • 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. – combinatorist Oct 21 '20 at 21:06

3 Answers3

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.

laconbass
  • 4,409
  • 4
  • 17
  • 20
heemayl
  • 56,300
  • any possible hacks? – Error Jun 22 '17 at 03:59
  • @Error, use temporary file to store the variable – Romeo Ninov Jun 22 '17 at 05: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. – Andrew Henle Jun 22 '17 at 10:06
  • @RomeoNinov edited – Error Jun 22 '17 at 13:54
  • @AndrewHenle will consider that, question edited though. – Error Jun 22 '17 at 13:57
  • Incorrect answer, it is possible: https://unix.stackexchange.com/questions/38205/change-environment-of-a-running-process/502129#502129 – Andry Feb 22 '19 at 17:18
  • 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 Feb 22 '19 at 17:48
  • @heemayl My answer describes exactly how to pass a variable from child shell script to parent shell script, so it can be solved that way, and yes it is posssible. All you have to do is just save a variable into described file or files. So, you answered as it is not possible, which in general is incorrect answer. – Andry Feb 23 '19 at 15:19
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
dan
  • 933
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).

  • 1
    you could use eval as well – hbogert Aug 17 '20 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. – G-Man Says 'Reinstate Monica' Aug 17 '20 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 Aug 17 '20 at 13:04
  • Hey, how do you know that child_prog will be invoked with file descriptor 1 pointing to a pipe? – clapas Aug 19 '20 at 12:55
  • @clapas:  Well, technically, I don’t.  But what, exactly, are you asking?  Do you understand Command Substitution? – G-Man Says 'Reinstate Monica' Aug 20 '20 at 02:20
  • 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 Aug 20 '20 at 09:42