Exporting variables from scripts
Often, when you’re doing something in an interactive shell, you’d like to set an environment variable from a script. But you can’t, because scripts run as their own process, which means that they can’t set environment variables in their parent (the shell). How do we get around that?
Motivation
The one that’s on my mind right now is that every company I’ve worked for recently has a script to log into their AWS
cloud, which results in setting the AWS_SESSION_TOKEN
, AWS_ACCESS_KEY_ID
, etc., environment variables.
Another example is where a script starts a background process, but needs to record its PID in the environment, so that
other programs can access it. ssh-agent
is an example of this; it exports the SSH_AUTH_SOCK
and SSH_AGENT_PID
environment variables.
Yet another example is something like rvm
or nvm
. nvm
, for example, manipulates $PATH
to add or update an entry that points to $NVM_VERSION_DIR/bin
.
Using eval
The way that ssh-agent
does it is by requiring the user to run it using eval
, as follows:
eval `ssh-agent`
This causes the shell to run the command in backticks – or $(...)
if you’d prefer – and then to evaluate the output as if it was commands entered at the prompt.
Running ssh-agent
without the eval
gives you something like the following:
SSH_AUTH_SOCK=/var/some-gibberish/ssh-whatever/agent.92365; export SSH_AUTH_SOCK;
SSH_AGENT_PID=92365; export SSH_AGENT_PID;
echo Agent pid 92365;
…which, when the shell executes them, will set the two environment variables and output a message.
Another example of using eval
is the way that, say, direnv hook zsh
(or direnv hook bash
) or kubectl completion
zsh
work: as with ssh-agent
, they output shell commands that set up various functions, aliases, hooks and so on.
Functions
Functions can also set environment variables. For example:
% hello() { HELLO=$1 }
% hello joe
% echo $HELLO
joe
% hello robert
% echo $HELLO
robert
Using source
To get our shell to load those functions, we should use the source
command. Where eval
runs the command and
evaluates the output, source
just evaluates the file directly.
This is the mechanism that, for example, nvm
uses. You add the following to your profile:
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
Note that this uses .
as a shorthand for source
, and it escapes it with \
, which prevents it from being treated as
an alias.
So we can create a script, for example:
# scripts/hello.sh
hello() { HELLO=$1 }
…and then source
it in our .bashrc
, for example:
[ -s "$HOME/scripts/hello.sh" && source "$HOME/scripts/hello.sh" ]
Detecting whether we’re being run directly
It can sometimes be useful to be able to tell the difference between being source
-ed or eval
-ed. For example, you
might want the hello
command to print help, or do something actually useful if it’s run directly.
For details, start with this Stack Overflow question: https://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced.